diff --git a/.travis.yml b/.travis.yml index 35e3d75a..9474f561 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,12 @@ language: go go: - 1.13.x +before install: + - sudo docker run --rm -it -d -p 8082:8080 -p 9082:9080 -p 8000:8000 -v ~/dgraph:/dgraph fogflow/dgraph:latest + - sudo docker run --rm -it -d --name mongodb -d mongo:3.4 + - sudo docker run --rm -it -d --name orion1 --link mongodb:mongodb -p 1026:1026 fiware/orion -dbhost mongodb + - docker ps -a + install: - docker --version @@ -33,11 +39,76 @@ install: - pwd - sh build + - sudo apt-get update + - sudo apt-get install python-pip + - pip -V + - sudo pip install --upgrade pip + - pip install Flask + - pip install requests + - pip install -U pytest + - sudo apt-get install curl + - curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - + - sudo apt-get install nodejs + - node -v + - npm -v + - echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list + - wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add - + - sudo apt-get update + - sudo apt-get install rabbitmq-server + - sudo update-rc.d rabbitmq-server defaults + - sudo service rabbitmq-server start + - sudo systemctl enable rabbitmq-server + - sudo systemctl start rabbitmq-server + - sudo rabbitmqctl add_user admin mypass + - sudo rabbitmqctl set_user_tags admin administrator + - sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*" + - echo "Done" + before_script: - go get -u golang.org/x/lint/golint script: + - cd ../ + - cd discovery/ + - go get + - go build + - screen -d -m ./discovery + - cd ../ + - cd broker/ + - go get + - go build + - screen -d -m ./broker + - cd ../ + - cd master/ + - go get + - go build + - screen -d -m ./master + - cd ../ + - cd worker/ + - go get + - go build + - screen -d -m ./worker + - cd ../ + - cd designer/ + - npm install + - screen -d -m node main.js + - cd ../ + - cd test/UnitTest/ + - screen -d -m python accumulator.py + - cd ../ + - cd UnitTest/v2/ + - pytest -s -v + - cd ../ + - cd v1/ + - pytest -s -v + - cd ../ + - cd NGSI-LD/ + - pytest -s -v + - cd ../ + - cd persistance/ + - pytest -s -v + - echo "Testing Done !" notifications: email: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..ed18be84 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,19 @@ +## Contributing to FogFlow + + +### sign up your contributor agreement license + +If you are interested to make any contribution to FogFlow, please download the contributor license agreement listed below and send us you signed agreement via email fogflow@listserv.neclab.eu + + +[FogFlow Entity Contributor License Agreement](https://github.com/smartfog/fogflow/blob/development/FogFlow-Entity.pdf) + + +[FogFlow Individual Contributor License Agreement](https://github.com/smartfog/fogflow/blob/development/FogFlow-Individual.pdf) + +### contribute code to FogFlow + +*Before opening a pull request*, review the +[Contributing to FogFlow guide](https://fogflow.readthedocs.io/en/latest/guideline.html). + + diff --git a/FogFlow-Entity.pdf b/FogFlow-Entity.pdf new file mode 100644 index 00000000..4f35c939 Binary files /dev/null and b/FogFlow-Entity.pdf differ diff --git a/FogFlow-Individual.pdf b/FogFlow-Individual.pdf new file mode 100644 index 00000000..0c276362 Binary files /dev/null and b/FogFlow-Individual.pdf differ diff --git a/application/operator/face-counter/main.py b/application/operator/face-counter/main.py index b6f1404b..2ea7ee9d 100644 --- a/application/operator/face-counter/main.py +++ b/application/operator/face-counter/main.py @@ -72,7 +72,7 @@ def object2Element(ctxObj): ctxElement = {} ctxElement['entityId'] = ctxObj['entityId']; - + ctxElement['attributes'] = [] if 'attributes' in ctxObj: for key in ctxObj['attributes']: diff --git a/application/template/NGSILD/python/build b/application/template/NGSILD/python/build new file mode 100755 index 00000000..51529d69 --- /dev/null +++ b/application/template/NGSILD/python/build @@ -0,0 +1 @@ +docker build -t "python-fog-function" . diff --git a/application/template/NGSILD/python/config.json b/application/template/NGSILD/python/config.json new file mode 100644 index 00000000..21f7a681 --- /dev/null +++ b/application/template/NGSILD/python/config.json @@ -0,0 +1,8 @@ +[{ + "command": "CONNECT_BROKER", + "brokerURL": "http://180.179.214.208:8070" +}, { + "command": "SET_OUTPUTS", + "id": "Stream.ChildFound.01", + "type": "ChildFound" +}] diff --git a/application/template/NGSILD/python/dockerfile b/application/template/NGSILD/python/dockerfile new file mode 100644 index 00000000..dc22687e --- /dev/null +++ b/application/template/NGSILD/python/dockerfile @@ -0,0 +1,10 @@ +FROM python:2.7-alpine + +RUN mkdir /task +ADD main.py /task +ADD requirements.txt /task +WORKDIR /task + +RUN pip install -r requirements.txt + +CMD ["python", "./main.py"] diff --git a/application/template/NGSILD/python/main.py b/application/template/NGSILD/python/main.py new file mode 100644 index 00000000..02da11ae --- /dev/null +++ b/application/template/NGSILD/python/main.py @@ -0,0 +1,206 @@ +from flask import Flask, jsonify, abort, request, make_response +import requests +import json +import time +import datetime +import threading +import os + +app = Flask(__name__, static_url_path='') + +# global variables + +brokerURL = '' +outputs = [] +timer = None +lock = threading.Lock() +counter = 0 +create = 0 + + +@app.errorhandler(400) +def not_found(error): + return make_response(jsonify({'error': 'Bad request'}), 400) + + +@app.errorhandler(404) +def not_found(error): + return make_response(jsonify({'error': 'Not found'}), 404) + + +@app.route('/admin', methods=['POST']) +def admin(): + if not request.json: + abort(400) + + configObjs = request.json + handleConfig(configObjs) + + return jsonify({'responseCode': 200}) + + +@app.route('/notifyContext', methods=['POST']) +def notifyContext(): + print '=============notify=============' + if not request.json: + abort(400) + + objs = readContextElements(request.json) + global counter + counter = counter + 1 + + print objs + + handleNotify(objs) + + return jsonify({'responseCode': 200}) + + +def element2Object(element): + ctxObj = {} + for key in element: + ctxObj[key] = element[key] + return ctxObj + + +def object2Element(ctxObj): + ctxElement = {} + + for key in ctxObj: + ctxElement[key] = ctxObj[key] + return ctxElement + + +def readContextElements(data): + + ctxObjects = [] + + for response in data['contextResponses']: + if response['statusCode']['code'] == 200: + ctxObj = element2Object(response['contextElement']) + ctxObjects.append(ctxObj) + return ctxObjects + + +def handleNotify(contextObjs): + for ctxObj in contextObjs: + processInputStreamData(ctxObj) + + +def processInputStreamData(obj): + print '===============receive context entity====================' + print obj + + global counter + counter = counter + 1 + + +def handleConfig(configurations): + global brokerURL + global num_of_outputs + for config in configurations: + if config['command'] == 'CONNECT_BROKER': + brokerURL = config['brokerURL'] + if config['command'] == 'SET_OUTPUTS': + outputs.append({'id': config['id'], 'type': config['type']}) + + +def handleTimer(): + global timer + + # publish the counting result + + entity = {} + entity['id'] = 'urn:ngsi-ld:result.01' + entity['type'] = 'Result' + entity['counter'] = counter + + publishResult(entity) + timer = threading.Timer(10, handleTimer) + timer.start() + + +# update request on broker + +def update(resultCtxObj): + global brokerURL + if brokerURL.endswith('/ngsi10') == True: + brokerURL = brokerURL.rsplit('/', 1)[0] + if brokerURL == '': + return + + ctxElement = object2Element(resultCtxObj) + + id = ctxElement['id'] + ctxElement.pop('id') + ctxElement.pop('type') + headers = {'Accept': 'application/ld+json', + 'Content-Type': 'application/ld+json'} + response = requests.patch(brokerURL + '/ngsi-ld/v1/entities/' + id + + '/attrs', data=json.dumps(ctxElement), + headers=headers) + return response.status_code + + +def sendDataToBroker(resultCtxObj): + global create + if create == 0: + response = cretaeRequest(resultCtxObj) + if response != 201: + print 'failed to send the create request' + else: + print 'Entity already has been created trying for update request....' + response = update(resultCtxObj) + if response != 204: + print 'failed to send the updte request' + + +def publishResult(result): + resultCtxObj = {} + resultCtxObj['id'] = result['id'] + resultCtxObj['type'] = result['type'] + data = {} + data['type'] = 'Property' + data['value'] = result['counter'] + resultCtxObj['count'] = data + sendDataToBroker(resultCtxObj) + + +# create request on broker + +def cretaeRequest(ctxObj): + global brokerURL + global create + if brokerURL.endswith('/ngsi10') == True: + brokerURL = brokerURL.rsplit('/', 1)[0] + if brokerURL == '': + return + + ctxElement = object2Element(ctxObj) + + headers = {'Accept': 'application/ld+json', + 'Content-Type': 'application/ld+json', + 'Link': '<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + response = requests.post(brokerURL + '/ngsi-ld/v1/entities/', + data=json.dumps(ctxElement), + headers=headers) + if response.status_code == 201: + create = create + 1 + if response.status_code != 201: + create = 1 + return response.status_code + + +if __name__ == '__main__': + handleTimer() + + myport = int(os.environ['myport']) + + myCfg = os.environ['adminCfg'] + adminCfg = json.loads(myCfg) + handleConfig(adminCfg) + + app.run(host='0.0.0.0', port=myport) + + # timer.cancel() + diff --git a/application/template/NGSILD/python/requirements.txt b/application/template/NGSILD/python/requirements.txt new file mode 100644 index 00000000..beb819fe --- /dev/null +++ b/application/template/NGSILD/python/requirements.txt @@ -0,0 +1,2 @@ +Flask==1.0.2 +requests>=2.20.0 diff --git a/application/template/NGSILD/python/subscription.json b/application/template/NGSILD/python/subscription.json new file mode 100644 index 00000000..203b88f6 --- /dev/null +++ b/application/template/NGSILD/python/subscription.json @@ -0,0 +1,10 @@ +{ + "entities": [ + { + "type": "Camera", + "isPattern": false, + "id": "Stream.Camera.02" + } + ], + "reference": "http://192.168.1.102:36772" +} diff --git a/application/template/NGSILD/python/test.sh b/application/template/NGSILD/python/test.sh new file mode 100644 index 00000000..6d220247 --- /dev/null +++ b/application/template/NGSILD/python/test.sh @@ -0,0 +1,10 @@ +#start a container for test +#docker run -p 8009:8080 -t -i facefinder /bin/bash + +#configurate +curl -X POST "http://192.168.1.80:8009/admin" -d @config.json --header "Content-Type:application/json" --header "Accept:application/json" + +#issue a subscription to get the input data +curl -X POST "http://192.168.1.80:8091/ngsi10/subscribeContext" -d @subscriptionCamera.json --header "Content-Type:application/json" --header "Accept:application/json" +curl -X POST "http://192.168.1.80:8091/ngsi10/subscribeContext" -d @subscriptionChildLost.json --header "Content-Type:application/json" --header "Accept:application/json" + diff --git a/broker/Dockerfile b/broker/Dockerfile index 365de813..f60050b5 100644 --- a/broker/Dockerfile +++ b/broker/Dockerfile @@ -1,4 +1,4 @@ -FROM scratch +FROM alpine ADD broker / CMD ["/broker"] diff --git a/broker/restapisrv.go b/broker/restapisrv.go index fd14eb7b..939c34ab 100644 --- a/broker/restapisrv.go +++ b/broker/restapisrv.go @@ -53,10 +53,31 @@ func (apisrv *RestApiSrv) Start(cfg *Config, broker *ThinBroker) { rest.Get("/ngsi10/subscription/#sid", apisrv.getSubscription), rest.Delete("/ngsi10/subscription/#sid", apisrv.deleteSubscription), - //NGSIV2 + //NGSIV2 APIs rest.Get("/v2/subscriptions", apisrv.getv2Subscriptions), rest.Get("/v2/subscription/#sid", apisrv.getv2Subscription), rest.Delete("/v2/subscription/#sid", apisrv.deletev2Subscription), + + //NGSI-LD APIs + rest.Post("/ngsi-ld/v1/entities/", broker.LDCreateEntity), + rest.Get("/ngsi-ld/v1/entities/#eid", apisrv.LDGetEntity), + rest.Get("/ngsi-ld/v1/entities", apisrv.GetQueryParamsEntities), + rest.Post("/ngsi-ld/v1/entities/#eid/attrs", broker.LDAppendEntityAttributes), + rest.Patch("/ngsi-ld/v1/entities/#eid/attrs", broker.LDUpdateEntityAttributes), + rest.Patch("/ngsi-ld/v1/entities/#eid/attrs/#attr", broker.LDUpdateEntityByAttribute), + rest.Delete("/ngsi-ld/v1/entities/#eid", apisrv.DeleteLDEntity), + rest.Delete("/ngsi-ld/v1/entities/#eid/attrs/#attr", broker.LDDeleteEntityAttribute), + + rest.Post("/ngsi-ld/v1/csourceRegistrations/", broker.RegisterCSource), + rest.Patch("/ngsi-ld/v1/csourceRegistrations/#rid", broker.UpdateCSourceRegistration), + rest.Delete("/ngsi-ld/v1/csourceRegistrations/#rid", apisrv.DeleteCSourceRegistration), + rest.Get("/ngsi-ld/v1/csourceRegistrations", apisrv.GetQueryParamsRegistrations), + + rest.Post("/ngsi-ld/v1/subscriptions/", broker.LDCreateSubscription), + rest.Get("/ngsi-ld/v1/subscriptions/", broker.GetLDSubscriptions), + rest.Get("/ngsi-ld/v1/subscriptions/#sid", apisrv.GetLDSubscription), + rest.Patch("/ngsi-ld/v1/subscriptions/#sid", broker.UpdateLDSubscription), + rest.Delete("/ngsi-ld/v1/subscriptions/#sid", apisrv.DeleteLDSubscription), ) if err != nil { log.Fatal(err) @@ -270,3 +291,241 @@ func (apisrv *RestApiSrv) deleteRegistration(w rest.ResponseWriter, r *rest.Requ w.WriteHeader(400) } } + +// NGSI-LD starts from here. + +func (apisrv *RestApiSrv) DeleteLDEntity(w rest.ResponseWriter, r *rest.Request) { + var eid = r.PathParam("eid") + if ctype, accept := r.Header.Get("Content-Type"), r.Header.Get("Accept"); (ctype == "application/json" || ctype == "application/ld+json") && accept == "application/ld+json" { + err := apisrv.broker.ldDeleteEntity(eid) + if err == nil { + w.WriteHeader(204) + } else { + rest.Error(w, err.Error(), 404) + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", http.StatusBadRequest) + return + } +} + +func (apisrv *RestApiSrv) LDGetEntity(w rest.ResponseWriter, r *rest.Request) { + var eid = r.PathParam("eid") + if ctype, accept := r.Header.Get("Content-Type"), r.Header.Get("Accept"); ctype == "application/ld+json" || accept == "application/ld+json" || accept == "application/ld+json" || accept == "application/*" || accept == "application/json" || accept == "*/*" { + entity := apisrv.broker.ldGetEntity(eid) + if entity != nil { + if accept == "application/json" || accept == " "{ + w.Header().Set("Content-Type","application/json") + }else { + w.Header().Set("Content-Type","application/ld+json") + } + w.WriteHeader(200) + w.WriteJson(entity) + } else { + w.WriteHeader(404) + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", http.StatusBadRequest) + return + } +} + +// To get query parameters from NGSI-LD Entity Query Requests +func (apisrv *RestApiSrv) GetQueryParamsEntities(w rest.ResponseWriter, r *rest.Request) { + queryValues := r.URL.Query() + if ctype, accept := r.Header.Get("Content-Type"), r.Header.Get("Accept"); ctype == "application/ld+json" && accept == "application/ld+json" { + if attrs, ok := queryValues["attrs"]; ok == true { + if len(queryValues) > 1 { + rest.Error(w, "Query parameters other than attrs are not supported in this request!", 400) + return + } + if attrs[0] == "" { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + entities := apisrv.broker.ldEntityGetByAttribute(attrs) + if len(entities) > 0 { + w.WriteHeader(200) + w.WriteJson(entities) + } else { + w.WriteHeader(404) + } + return + } else if typ, ok := queryValues["type"]; ok == true { + if typ[0] == "" { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + if eid, ok := queryValues["id"]; ok == true { + if len(queryValues) > 2 { + rest.Error(w, "Query parameters other than id and type are not supported in this request!", 400) + return + } + if eid[0] == "" { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + entities := apisrv.broker.ldEntityGetById(eid, typ) + if len(entities) > 0 { + w.WriteHeader(200) + w.WriteJson(entities) + } else { + w.WriteHeader(404) + } + return + } + if idPattern, ok := queryValues["idPattern"]; ok == true { + if len(queryValues) > 2 { + rest.Error(w, "Query parameters other than idPattern and type are not supported in this request!", 400) + return + } + if idPattern[0] == "" { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + entities := apisrv.broker.ldEntityGetByIdPattern(idPattern, typ) + if len(entities) > 0 { + w.WriteHeader(200) + w.WriteJson(entities) + } else { + w.WriteHeader(404) + } + return + } + + if len(queryValues) > 1 { + rest.Error(w, "Query parameters other than type are not supported in this request!", 400) + return + } + link := r.Header.Get("Link") + entities, err := apisrv.broker.ldEntityGetByType(typ, link) + if err != nil { + w.WriteHeader(500) + } else { + if len(entities) > 0 { + w.WriteHeader(200) + w.WriteJson(entities) + } else { + w.WriteHeader(404) + } + return + } + } else { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", http.StatusBadRequest) + return + } +} + +// To get query parameters from CSource Registration Query Requests +func (apisrv *RestApiSrv) GetQueryParamsRegistrations(w rest.ResponseWriter, r *rest.Request) { + queryValues := r.URL.Query() + if ctype, accept := r.Header.Get("Content-Type"), r.Header.Get("Accept"); ctype == "application/ld+json" && accept == "application/ld+json" { + if typ, ok := queryValues["type"]; ok == true { + if typ[0] == "" { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + if eid, ok := queryValues["id"]; ok == true { + if len(queryValues) > 2 { + rest.Error(w, "Query parameters other than id and type are not supported in this request!", 400) + return + } + if eid[0] == "" { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + registrations := apisrv.broker.getCSourceRegByIdAndType(eid, typ) + if len(registrations) > 0 { + w.WriteHeader(200) + w.WriteJson(registrations) + } else { + w.WriteHeader(404) + } + return + } + if idPattern, ok := queryValues["idPattern"]; ok == true { + if len(queryValues) > 2 { + rest.Error(w, "Query parameters other than idPattern and type are not supported in this request!", 400) + return + } + if idPattern[0] == "" { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + return + } + registrations := apisrv.broker.getCSourceRegByIdPatternAndType(idPattern, typ) + if len(registrations) > 0 { + w.WriteHeader(200) + w.WriteJson(registrations) + } else { + w.WriteHeader(404) + } + return + } + if len(queryValues) > 1 { + rest.Error(w, "Query parameters other than type are not supported in this request!", 400) + return + } + link := r.Header.Get("Link") + registrations, err := apisrv.broker.getCSourceRegByType(typ, link) + if err != nil { + rest.Error(w, "Internal Server Error!", http.StatusInternalServerError) + return + } else { + if len(registrations) > 0 { + w.WriteHeader(200) + w.WriteJson(registrations) + } else { + w.WriteHeader(404) + } + return + } + } else { + rest.Error(w, "Incorrect or missing query parameters!", http.StatusBadRequest) + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", http.StatusBadRequest) + } +} + +func (apisrv *RestApiSrv) DeleteCSourceRegistration(w rest.ResponseWriter, r *rest.Request) { + rid := r.PathParam("rid") + err := apisrv.broker.deleteCSourceRegistration(rid) + if err != nil { + w.WriteHeader(404) + } else { + w.WriteHeader(204) + } +} + +func (apisrv *RestApiSrv) GetLDSubscription(w rest.ResponseWriter, r *rest.Request) { + if accept := r.Header.Get("Accept"); accept == "application/ld+json" { + sid := r.PathParam("sid") + subscription := apisrv.broker.getLDSubscription(sid) + if subscription != nil { + w.WriteHeader(200) + w.WriteJson(subscription) + } else { + w.WriteHeader(404) + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", http.StatusBadRequest) + } +} + +func (apisrv *RestApiSrv) DeleteLDSubscription(w rest.ResponseWriter, r *rest.Request) { + sid := r.PathParam("sid") + err := apisrv.broker.deleteLDSubscription(sid) + if err != nil { + if err.Error() == "NotFound" { + w.WriteHeader(404) + } else { + rest.Error(w, err.Error(), http.StatusInternalServerError) + } + } else { + w.WriteHeader(204) + } +} diff --git a/broker/serializer.go b/broker/serializer.go new file mode 100644 index 00000000..f7c394a7 --- /dev/null +++ b/broker/serializer.go @@ -0,0 +1,784 @@ +package main + +import ( + "errors" + . "github.com/smartfog/fogflow/common/ngsi" + "strings" + "time" +) + +type Serializer struct{} + +/* +func (sz Serializer) SerializeEntity(ctxElem *LDContextElement) (map[string]interface{}, error) { + jobj := make(map[string]interface{}) + jobj["id"] = ctxElem.Id + jobj["type"] = ctxElem.Type + jobj["createdAt"] = ctxElem.CreatedAt + // jobj["modifiedAt"] = ctxElem.ModifiedAt + jobj["location"] = sz.serializeLocation(ctxElem.Location) + for _, property := range ctxElem.Properties { + jobj[property.Name] = sz.serializeProperty(property) + } + for _, relationship := range ctxElem.Relationships { + jobj[relationship.Name] = sz.serializeRelationship(relationship) + } + return jobj, nil +} + +func (sz Serializer) serializeProperty(property Property) map[string]interface{} { + serializedProperty := make(map[string]interface{}) + serializedProperty["type"] = "Property" + serializedProperty["value"] = property.Value + // serializedProperty["createdAt"] = property.CreatedAt + // serializedProperty["modifiedAt"] = property.ModifiedAt + if property.ObservedAt != "" { + serializedProperty["observedAt"] = property.ObservedAt + } + if property.DatasetId != "" { + serializedProperty["datasetId"] = property.DatasetId + } + if property.InstanceId != "" { + serializedProperty["instanceId"] = property.InstanceId + } + if property.UnitCode != "" { + serializedProperty["unitcode"] = property.UnitCode + } + if property.ProvidedBy.Type != "" && property.ProvidedBy.Object != "" { + serializedProperty["providedBy"] = sz.serializeProvidedBy(property.ProvidedBy) + } + for _, propertyNested := range property.Properties { + serializedProperty[propertyNested.Name] = sz.serializeProperty(propertyNested) + } + for _, relationshipNested := range property.Relationships { + serializedProperty[relationshipNested.Name] = sz.serializeRelationship(relationshipNested) + } + return serializedProperty +} + +func (sz Serializer) serializeRelationship(relationship Relationship) map[string]interface{} { + serializedRelationship := make(map[string]interface{}) + serializedRelationship["type"] = "Relationship" + serializedRelationship["object"] = relationship.Object + // serializedRelationship["createdAt"] = relationship.CreatedAt + // serializedRelationship["modifiedAt"] = relationship.ModifiedAt + if relationship.ObservedAt != "" { + serializedRelationship["observedAt"] = relationship.ObservedAt + } + if relationship.DatasetId != "" { + serializedRelationship["datasetId"] = relationship.DatasetId + } + if relationship.InstanceId != "" { + serializedRelationship["instanceId"] = relationship.InstanceId + } + if relationship.ProvidedBy.Type != "" && relationship.ProvidedBy.Object != "" { + serializedRelationship["providedBy"] = sz.serializeProvidedBy(relationship.ProvidedBy) + } + for _, propertyNested := range relationship.Properties { + serializedRelationship[propertyNested.Name] = sz.serializeProperty(propertyNested) + } + for _, relationshipNested := range relationship.Relationships { + serializedRelationship[relationshipNested.Name] = sz.serializeRelationship(relationshipNested) + } + return serializedRelationship +} + +func (sz Serializer) serializeProvidedBy(providedBy ProvidedBy) map[string]interface{} { + serializedProvidedBy := make(map[string]interface{}) + if strings.Contains(providedBy.Type, "Property") || strings.Contains(providedBy.Type, "property") { + serializedProvidedBy["type"] = "Property" + } else if strings.Contains(providedBy.Type, "Relationship") || strings.Contains(providedBy.Type, "relationship") { + serializedProvidedBy["type"] = "Relationship" + } else if strings.Contains(providedBy.Type, "/") { + serializedProvidedBy["type"] = sz.afterString(providedBy.Type, "/") + } + serializedProvidedBy["object"] = providedBy.Object + return serializedProvidedBy +} + +func (sz Serializer) serializeLocation(location LDLocation) map[string]interface{} { + serializedLocation := make(map[string]interface{}) + if strings.Contains(location.Type, "GeoProperty") { + serializedLocation["type"] = "GeoProperty" + } + if locationValueMap, ok := location.Value.(LDLocationValue); ok == true { + // Type is LDLocationValue + serializedLocation["value"] = sz.serializeLocationValue(locationValueMap) + } else { + // Type is string + serializedLocation["value"] = location.Value + } + return serializedLocation +} + +func (sz Serializer) serializeLocationValue(location LDLocationValue) map[string]interface{} { + locationValue := make(map[string]interface{}) + if strings.Contains(location.Type, "Point") { + locationValue["type"] = "Point" + } else if strings.Contains(location.Type, "LineString") { + locationValue["type"] = "LineString" + } else if strings.Contains(location.Type, "Polygon") { + locationValue["type"] = "Polygon" + } else if strings.Contains(location.Type, "MultiPoint") { + locationValue["type"] = "MultiPoint" + } else if strings.Contains(location.Type, "MultiLineString") { + locationValue["type"] = "MultiLineString" + } else if strings.Contains(location.Type, "MultiPolygon") { + locationValue["type"] = "MultiPolygon" + } else if strings.Contains(location.Type, "GeometryCollection") { + locationValue["type"] = "GeometryCollection" + } + if !(strings.Contains(location.Type, "GeometryCollection")) { + locationValue["coordinates"] = location.Coordinates + } else { // Serialize GeometryCollection. + } + if len(location.Geometries) > 0 { // Serialize Geometries + } + return locationValue +} */ + +func (sz Serializer) DeSerializeEntity(expanded []interface{}) (map[string]interface{}, error) { + entity := make(map[string]interface{}) + for _, val := range expanded { + + stringsMap := val.(map[string]interface{}) + + for k, v := range stringsMap { + if strings.Contains(k, "id") { + if v != nil { + entity["id"] = sz.getId(v.(interface{})) + } + } else if strings.Contains(k, "type") { + if v != nil { + entity["type"] = sz.getType(v.([]interface{})) + } + } else if strings.Contains(k, "location") { + if v != nil { + entity["location"] = sz.getLocation(v.([]interface{})) + } + } else if strings.Contains(k, "createdAt") { + continue + // } else if strings.Contains(k, "context") { + // entity["@context"] = v + } else { // default cases like property, relationship here. + + interfaceArray := v.([]interface{}) + if len(interfaceArray) > 0 { + mp := interfaceArray[0].(map[string]interface{}) + typ := mp["@type"].([]interface{}) + if len(typ) > 0 { + if strings.Contains(typ[0].(string), "Property") { + + property, err := sz.getProperty(mp) + if err != nil { + return entity, err + } else { + entity[k] = property + } + } else if strings.Contains(typ[0].(string), "Relationship") { + + relationship, err := sz.getRelationship(mp) + if err != nil { + return entity, err + } else { + entity[k] = relationship + } + } + } + } + } + } + + } + entity["modifiedAt"] = time.Now().String() + return entity, nil +} + +func (sz Serializer) DeSerializeRegistration(expanded []interface{}) (CSourceRegistrationRequest, error) { + registration := CSourceRegistrationRequest{} + for _, val := range expanded { + stringsMap := val.(map[string]interface{}) + for k, v := range stringsMap { + if strings.Contains(k, "@id") { + if v != nil { + registration.Id = sz.getId(v.(interface{})) + } + } else if strings.Contains(k, "@type") { + if v != nil { + registration.Type = sz.getType(v.([]interface{})) + } + } else if strings.Contains(k, "timestamp") { // timestamp from payload is taken as observationInterval in given datatypes in spec: + if v != nil { + registration.ObservationInterval = sz.getTimeStamp(v.([]interface{})) + } + } else if strings.Contains(k, "description") { + if v != nil { + registration.Description = sz.getStringValue(v.([]interface{})) + } + } else if strings.Contains(k, "endpoint") { + if v != nil { + registration.Endpoint = sz.getStringValue(v.([]interface{})) + } + } else if strings.Contains(k, "expires") { + if v != nil { + registration.Expires = sz.getDateAndTimeValue(v.([]interface{})) + } + } else if strings.Contains(k, "information") { + if v != nil { + registration.Information = sz.getInformation(v.([]interface{})) + } + } else if strings.Contains(k, "location") { + if v != nil { + registration.Location = sz.getStringValue(v.([]interface{})) + } + } else if strings.Contains(k, "name") { + if v != nil { + registration.Name = sz.getStringValue(v.([]interface{})) + } + } else { + // CSource property name + } + } + } + registration.ModifiedAt = time.Now().String() + return registration, nil +} + +func (sz Serializer) DeSerializeSubscription(expanded []interface{}) (LDSubscriptionRequest, error) { + subscription := LDSubscriptionRequest{} + for _, val := range expanded { + stringsMap := val.(map[string]interface{}) + for k, v := range stringsMap { + if strings.Contains(k, "@id") { + if v != nil { + subscription.Id = sz.getId(v.(interface{})) + } + } else if strings.Contains(k, "@type") { + if v != nil { + subscription.Type = sz.getType(v.([]interface{})) + } + } else if strings.Contains(k, "description") { + if v != nil { + subscription.Description = sz.getValue(v.([]interface{})).(string) + } + } else if strings.Contains(k, "notification") { + if v != nil { + notification, err := sz.getNotification(v.([]interface{})) + if err != nil { + return subscription, err + } else { + subscription.Notification = notification + } + } + } else if strings.Contains(k, "entities") { + if v != nil { + subscription.Entities = sz.getEntities(v.([]interface{})) + } + } else if strings.Contains(k, "name") { + if v != nil { + subscription.Name = sz.getValue(v.([]interface{})).(string) + } + } else if strings.Contains(k, "watchedAttributes") { + if v != nil { + subscription.WatchedAttributes = sz.getArrayOfIds(v.([]interface{})) + } + } else { + // other subscription fields + } + } + } + subscription.ModifiedAt = time.Now().String() + subscription.IsActive = true + return subscription, nil +} + +func (sz Serializer) DeSerializeType(attrPayload []interface{}) string { + var attr string + if len(attrPayload) > 0 { + attrMap := attrPayload[0].(map[string]interface{}) + attrs := attrMap["@type"].([]interface{}) + attr = attrs[0].(string) + } + return attr +} + +func (sz Serializer) getId(id interface{}) string { + Id := id.(string) + return Id +} + +func (sz Serializer) getType(typ []interface{}) string { + + var Type, Type1 string + if len(typ) > 0 { + Type1 = typ[0].(string) + if strings.Contains(Type1, "GeoProperty") || strings.Contains(Type1, "geoproperty") { + Type = "GeoProperty" + } else if strings.Contains(Type1, "Point") || strings.Contains(Type1, "point") { + Type = "Point" + } else if strings.Contains(Type1, "Relationship") || strings.Contains(Type1, "relationship") { + Type = "Relationship" + } else if strings.Contains(Type1, "Property") || strings.Contains(Type1, "property") { + Type = "Property" + } else if strings.Contains(Type1, "person") || strings.Contains(Type1, "Person") { + Type = "Person" + } else { + Type = typ[0].(string) + } + } + return Type +} + +func (sz Serializer) getProperty(propertyMap map[string]interface{}) (map[string]interface{}, error) { + + Property := make(map[string]interface{}) + for propertyField, fieldValue := range propertyMap { + if strings.Contains(propertyField, "@type") { + if fieldValue != nil { + Property["type"] = sz.getType(fieldValue.([]interface{})) + } + } else if strings.Contains(propertyField, "hasValue") { + if fieldValue != nil { + Property["value"] = sz.getValueFromArray(fieldValue.([]interface{})) + if Property["value"] == "nil" || Property["value"] == "" { + err := errors.New("Property value can not be nil!") + return Property, err + } + }else { + err := errors.New("Property value can not be nil!") + return Property, err + } + } else if strings.Contains(propertyField, "observedAt") { + if fieldValue != nil { + Property["observedAt"] = sz.getDateAndTimeValue(fieldValue.([]interface{})) + } + } else if strings.Contains(propertyField, "datasetId") { + if fieldValue != nil { + Property["datasetId"] = sz.getDatasetId(fieldValue.([]interface{})) + } + } else if strings.Contains(propertyField, "instanceId") { + if fieldValue != nil { + Property["instanceId"] = sz.getInstanceId(fieldValue.([]interface{})) + } + } else if strings.Contains(propertyField, "unitCode") { + if fieldValue != nil { + Property["unitCode"] = sz.getUnitCode(fieldValue.([]interface{})) + } + } else if strings.Contains(propertyField, "providedBy") { + if fieldValue != nil { + Property["providedBy"] = sz.getProvidedBy(fieldValue.([]interface{})) + } + } else { // Nested property or relationship + + var typ string + nested := fieldValue.([]interface{}) + for _, val := range nested { + mp := val.(map[string]interface{}) + typInterface := mp["@type"].([]interface{}) + typ = typInterface[0].(string) + if strings.Contains(typ, "Property") { + property, err := sz.getProperty(mp) + if err != nil { + return Property, err + } else { + Property[propertyField] = property + } + } else if strings.Contains(typ, "Relationship") { + relationship, err := sz.getRelationship(mp) + if err != nil { + return Property, err + } else { + Property[propertyField] = relationship + } + } + } + } + } + //Property["modifiedAt"] = time.Now().String() + return Property, nil +} + +func (sz Serializer) getRelationship(relationshipMap map[string]interface{}) (map[string]interface{}, error) { + Relationship := make(map[string]interface{}) + for relationshipField, fieldValue := range relationshipMap { + if strings.Contains(relationshipField, "@type") { + if fieldValue != nil { + Relationship["type"] = sz.getType(fieldValue.([]interface{})) + } + } else if strings.Contains(relationshipField, "hasObject") { + if fieldValue != nil { + Relationship["object"] = sz.getIdFromArray(fieldValue.([]interface{})) + if Relationship["object"] == "nil" || Relationship["object"] == ""{ + err := errors.New("Relationship Object value can not be nil!") + return Relationship, err + } + }else { + err := errors.New("Relationship Object value can not be nil!") + return Relationship, err + } + } else if strings.Contains(relationshipField, "Object") { + if fieldValue != nil { + Relationship["object"] = sz.getValueFromArray(fieldValue.([]interface{})).(string) + } else { + err := errors.New("Relationship Object value can not be nil!") + return Relationship, err + } + } else if strings.Contains(relationshipField, "observedAt") { + if fieldValue != nil { + Relationship["observedAt"] = sz.getDateAndTimeValue(fieldValue.([]interface{})) + } + } else if strings.Contains(relationshipField, "providedBy") { + if fieldValue != nil { + Relationship["providedBy"] = sz.getProvidedBy(fieldValue.([]interface{})) + } + } else if strings.Contains(relationshipField, "datasetId") { + if fieldValue != nil { + Relationship["datasetId"] = sz.getDatasetId(fieldValue.([]interface{})) + } + } else if strings.Contains(relationshipField, "instanceId") { + if fieldValue != nil { + Relationship["instanceId"] = sz.getInstanceId(fieldValue.([]interface{})) + } + } else { // Nested property or relationship + var typ string + nested := fieldValue.([]interface{}) + for _, val := range nested { + mp := val.(map[string]interface{}) + typInterface := mp["@type"].([]interface{}) + typ = typInterface[0].(string) + + if strings.Contains(typ, "Property") { + property, err := sz.getProperty(mp) + if err != nil { + return Relationship, err + } else { + Relationship[relationshipField] = property + } + } else if strings.Contains(typ, "Relationship") { + relationship, err := sz.getRelationship(mp) + if err != nil { + return Relationship, err + } else { + Relationship[relationshipField] = relationship + } + } + } + } + } + //Relationship["modifiedAt"] = time.Now().String() + return Relationship, nil +} + +func (sz Serializer) getValue(hasValue []interface{}) interface{} { + + //Value := make(map[string]interface{}) + var Value interface{} + if len(hasValue) > 0 { + val := hasValue[0].(map[string]interface{}) + /*if val["@type"] != nil { + Value["Type"] = val["@type"].(string) + Value["Value"] = val["@value"].(interface{}) + } else { + Value["Value"] = hasValue[0] + }*/ + Value = val["@value"] + } + return Value +} + +func (sz Serializer) getValueFromArray(hasValue []interface{}) interface{} { + Value := make(map[string]interface{}) + var value interface{} + if len(hasValue) > 0 { + for _, oneValue := range hasValue { + if val := oneValue.(map[string]interface{}); val != nil { + + if val["@type"] != nil { + Value["Type"] = val["@type"].(string) + Value["Value"] = val["@value"].(interface{}) + return Value + } + value = val["@value"].(interface{}) //Value is overwritten, in case of multiple values in payload, value array is never returned.. + } + } + } + return value +} + +func (sz Serializer) getIdFromArray(object []interface{}) string { + var Id string + if len(object) > 0 { + hasObject := object[0].(map[string]interface{}) + Id = hasObject["@id"].(string) + } + return Id +} + +func (sz Serializer) getDateAndTimeValue(dateTimeValue []interface{}) string { + var DateTimeValue string + if len(dateTimeValue) > 0 { + observedAtMap := dateTimeValue[0].(map[string]interface{}) + if strings.Contains(observedAtMap["@value"].(string), "DateTime") { + DateTimeValue = observedAtMap["@value"].(string) + } + } + return DateTimeValue +} + +func (sz Serializer) getProvidedBy(providedBy []interface{}) ProvidedBy { + ProvidedBy := ProvidedBy{} + if len(providedBy) > 0 { + providedByMap := providedBy[0].(map[string]interface{}) + for k, v := range providedByMap { + if strings.Contains(k, "@type") { + ProvidedBy.Type = sz.getType(v.([]interface{})) + } else if strings.Contains(k, "hasObject") { + ProvidedBy.Object = sz.getIdFromArray(v.([]interface{})) + } + } + } + return ProvidedBy +} + +//DATASET_ID +func (sz Serializer) getDatasetId(datasetId []interface{}) string { + return "" +} + +//INSTANCE_ID +func (sz Serializer) getInstanceId(instanceId []interface{}) string { + return "" +} + +//UNIT_CODE +func (sz Serializer) getUnitCode(unitCode []interface{}) string { + var UnitCode string + if len(unitCode) > 0 { + unitCodeMap := unitCode[0].(map[string]interface{}) + UnitCode = unitCodeMap["@value"].(string) + } + return UnitCode +} + +//LOCATION +func (sz Serializer) getLocation(location []interface{}) LDLocation { + Location := LDLocation{} + if len(location) > 0 { + locationMap := location[0].(map[string]interface{}) + for k, v := range locationMap { + if strings.Contains(k, "@type") { + Location.Type = sz.getType(v.([]interface{})) + } else if strings.Contains(k, "hasValue") { + Location.Value = sz.getLocationValue(v.([]interface{})) + } + } + } + return Location +} + +func (sz Serializer) getLocationValue(locationValue []interface{}) interface{} { + if len(locationValue) > 0 { + locationValueMap := locationValue[0].(map[string]interface{}) + if locationValueMap["@value"] != nil { + valueScalar := locationValueMap["@value"].(interface{}) + stringValue := valueScalar.(string) + return stringValue + } else if locationValueMap["@type"] != nil { + LocationValue := LDLocationValue{} + for k, v := range locationValueMap { + if strings.Contains(k, "@type") { + LocationValue.Type = sz.getType(v.([]interface{})) + } + } + for k, v := range locationValueMap { + if strings.Contains(k, "coordinates") { + if v != nil { + if strings.Contains(LocationValue.Type, "Point") { + LocationValue.Coordinates = sz.getPointLocation(v.([]interface{})) + } else if strings.Contains(LocationValue.Type, "GeometryCollection") { + LocationValue.Geometries = sz.getGeometryCollectionLocation(v.([]interface{})) + } else if strings.Contains(LocationValue.Type, "LineString") || strings.Contains(LocationValue.Type, "Polygon") || strings.Contains(LocationValue.Type, "MultiPoint") || strings.Contains(LocationValue.Type, "MultiLineString") || strings.Contains(LocationValue.Type, "MultiPolygon") { + LocationValue.Coordinates = sz.getArrayofCoordinates(v.([]interface{})) + } + } + } + } + return LocationValue + } + } + return nil +} + +func (sz Serializer) getPointLocation(coordinates []interface{}) []float64 { + var Coordinates []float64 //contains longitude & latitude values in order. + + for _, v := range coordinates { + coord := v.(map[string]interface{}) + Coordinates = append(Coordinates, coord["@value"].(float64)) + } + return Coordinates +} + +func (sz Serializer) getArrayofCoordinates(coordinates []interface{}) [][]float64 { + var Coordinates [][]float64 //Array contains point coordinates with longitude & latitude values in order + for i := 0; i < len(coordinates); i = i + 2 { + var coord []float64 + coord = append(coord, coordinates[i].(float64)) + coord = append(coord, coordinates[i+1].(float64)) + Coordinates = append(Coordinates, coord) + } + return Coordinates +} + +func (sz Serializer) getGeometryCollectionLocation(geometries []interface{}) []Geometry { + Geometries := []Geometry{} + for _, val := range geometries { + geometry := Geometry{} + geometryValueMap := val.(map[string]interface{}) + for k, v := range geometryValueMap { + if strings.Contains(k, "@Type") { + geometry.Type = sz.getType(v.([]interface{})) + } else if strings.Contains(k, "coordinates") { + if strings.Contains(geometry.Type, "Point") { + geometry.Coordinates = sz.getPointLocation(v.([]interface{})) + } else { + geometry.Coordinates = sz.getArrayofCoordinates(v.([]interface{})) + } + } + } + Geometries = append(Geometries, geometry) + } + return Geometries +} + +func (sz Serializer) getInformation(information []interface{}) []RegistrationInfo { + regInfoArray := []RegistrationInfo{} + for _, val := range information { + infoVal := val.(map[string]interface{}) + regInfo := RegistrationInfo{} + for k, v := range infoVal { + if strings.Contains(k, "properties") { + if v != nil { + regInfo.Properties = sz.getArrayOfIds(v.([]interface{})) + } + } else if strings.Contains(k, "relationships") { + if v != nil { + regInfo.Relationships = sz.getArrayOfIds(v.([]interface{})) + } + } else if strings.Contains(k, "entities") { + if v != nil { + regInfo.Entities = sz.getEntities(v.([]interface{})) + } + } + } + regInfoArray = append(regInfoArray, regInfo) + } + return regInfoArray +} + +func (sz Serializer) getArrayOfIds(arrayOfIds []interface{}) []string { + var ArrayOfIds []string + for _, v := range arrayOfIds { + idValue := v.(map[string]interface{}) + id := idValue["@id"].(string) + ArrayOfIds = append(ArrayOfIds, id) + } + return ArrayOfIds +} + +func (sz Serializer) getEntities(entitiesArray []interface{}) []EntityId { + entities := []EntityId{} + for _, val := range entitiesArray { + entityId := EntityId{} + entityFields := val.(map[string]interface{}) + for k, v := range entityFields { + if strings.Contains(k, "@id") { + entityId.ID = sz.getId(v.(string)) + } else if strings.Contains(k, "@type") { + entityId.Type = sz.getType(v.([]interface{})) + } else if strings.Contains(k, "idPattern") { + entityId.IdPattern = sz.getStringValue(v.([]interface{})) + } + } + entities = append(entities, entityId) + } + return entities +} + +func (sz Serializer) getStringValue(value []interface{}) string { + var Value string + if len(value) > 0 { + val := value[0].(map[string]interface{}) + Value = val["@value"].(string) + } + return Value +} + +func (sz Serializer) getNotification(notificationArray []interface{}) (NotificationParams, error) { + notification := NotificationParams{} + for _, val := range notificationArray { + notificationFields := val.(map[string]interface{}) + for k, v := range notificationFields { + if strings.Contains(k, "attributes") { + notification.Attributes = sz.getArrayOfIds(v.([]interface{})) + } else if strings.Contains(k, "endpoint") { + endpoint, err := sz.getEndpoint(v.([]interface{})) + if err != nil { + return notification, err + } else { + notification.Endpoint = endpoint + } + } else if strings.Contains(k, "format") { + notification.Format = sz.getStringValue(v.([]interface{})) + } + } + } + return notification, nil +} + +func (sz Serializer) getEndpoint(endpointArray []interface{}) (Endpoint, error) { + endpoint := Endpoint{} + for _, val := range endpointArray { + endpointFields := val.(map[string]interface{}) + for k, v := range endpointFields { + if strings.Contains(k, "accept") { + if v != nil { + endpoint.Accept = sz.getStringValue(v.([]interface{})) + } + } else if strings.Contains(k, "uri") { + if v != nil { + endpoint.URI = sz.getStringValue(v.([]interface{})) + } else { + err := errors.New("URI can not be nil!") + return endpoint, err + } + } + } + } + return endpoint, nil +} + +func (sz Serializer) getTimeStamp(timestampArray []interface{}) TimeInterval { + timeInterval := TimeInterval{} + for _, timestamp := range timestampArray { + timestampMap := timestamp.(map[string]interface{}) + for k, v := range timestampMap { + if strings.Contains(k, "start") { + timeInterval.Start = sz.getDateAndTimeValue(v.([]interface{})) + } else if strings.Contains(k, "end") { + timeInterval.End = sz.getDateAndTimeValue(v.([]interface{})) + } + } + } + return timeInterval +} + +func (sz Serializer) afterString(str string, markingStr string) string { + // Get sub-string after markingStr string + li := strings.LastIndex(str, markingStr) + liAdjusted := li + len(markingStr) + if liAdjusted >= len(str) { + return "" + } + return str[liAdjusted:len(str)] +} diff --git a/broker/thinBroker.go b/broker/thinBroker.go index f85bb8f0..1a09b7ab 100644 --- a/broker/thinBroker.go +++ b/broker/thinBroker.go @@ -1,15 +1,22 @@ package main import ( + "encoding/json" + "errors" + "fmt" "github.com/ant0ine/go-json-rest/rest" + "github.com/piprate/json-gold/ld" "github.com/satori/go.uuid" . "github.com/smartfog/fogflow/common/config" + . "github.com/smartfog/fogflow/common/constants" . "github.com/smartfog/fogflow/common/datamodel" . "github.com/smartfog/fogflow/common/ngsi" + "io/ioutil" "net/http" "strconv" "strings" "sync" + "time" ) type ThinBroker struct { @@ -56,6 +63,22 @@ type ThinBroker struct { //counter of heartbeat counter int64 + + //NGSI-LD feature addition + ldEntities map[string]interface{} // to map Entity Id with LDContextElement. + ldEntities_lock sync.RWMutex + + ldContextRegistrations map[string]CSourceRegistrationRequest // to map Registration Id with CSourceRegistrationRequest. + ldContextRegistrations_lock sync.RWMutex + + ldEntityID2RegistrationID map[string]string //to map the Entity IDs with their registration id. + ldEntityID2RegistrationID_lock sync.RWMutex + + ldSubscriptions map[string]*LDSubscriptionRequest // to map Subscription Id with LDSubscriptionRequest. + ldSubscriptions_lock sync.RWMutex + + tmpNGSIldNotifyCache []string + tmpNGSILDNotifyCache map[string]*NotifyContextAvailabilityRequest } func (tb *ThinBroker) Start(cfg *Config) { @@ -95,6 +118,13 @@ func (tb *ThinBroker) Start(cfg *Config) { tb.myProfile.BID = tb.myEntityId tb.myProfile.MyURL = tb.MyURL + // NGSI-LD feature addition + tb.ldEntities = make(map[string]interface{}) + tb.ldContextRegistrations = make(map[string]CSourceRegistrationRequest) + tb.ldEntityID2RegistrationID = make(map[string]string) + tb.ldSubscriptions = make(map[string]*LDSubscriptionRequest) + tb.tmpNGSILDNotifyCache = make(map[string]*NotifyContextAvailabilityRequest) + // register itself to the IoT discovery tb.registerMyself() @@ -471,7 +501,6 @@ func (tb *ThinBroker) UpdateContext(w rest.ResponseWriter, r *rest.Request) { if r.Header.Get("User-Agent") == "lightweight-iot-broker" { tb.handleInternalUpdateContext(&updateCtxReq) } - //Southbound feature addition if r.Header.Get("fiware-service") != "" && r.Header.Get("fiware-servicepath") != "" { fs := r.Header.Get("fiware-service") @@ -755,17 +784,32 @@ func (tb *ThinBroker) notifySubscribers(ctxElem *ContextElement, checkSelectedAt } func (tb *ThinBroker) notifyOneSubscriberWithCurrentStatus(entities []EntityId, sid string) { - elements := make([]ContextElement, 0) // check if the subscription still exists; if yes, then find out the selected attribute list tb.subscriptions_lock.RLock() - subscription, ok := tb.subscriptions[sid] + v1Subscription, ok := tb.subscriptions[sid] if ok == false { tb.subscriptions_lock.RUnlock() - return + tb.ldSubscriptions_lock.RLock() + ldSubscription, ldOK := tb.ldSubscriptions[sid] + + if ldOK == false { + tb.ldSubscriptions_lock.RUnlock() + return + } + selectedAttributes := ldSubscription.WatchedAttributes + tb.ldSubscriptions_lock.RUnlock() + tb.notifyOneSubscriberWithCurrentStatusOfLD(entities, sid, selectedAttributes) + } else { + selectedAttributes := v1Subscription.Attributes + tb.subscriptions_lock.RUnlock() + tb.notifyOneSubscriberWithCurrentStatusOfV1(entities, sid, selectedAttributes) } - selectedAttributes := subscription.Attributes - tb.subscriptions_lock.RUnlock() +} + +func (tb *ThinBroker) notifyOneSubscriberWithCurrentStatusOfV1(entities []EntityId, sid string, selectedAttributes []string) { + // Create NGSIv1 Context Element + elements := make([]ContextElement, 0) tb.entities_lock.Lock() for _, entity := range entities { @@ -1026,10 +1070,10 @@ func (tb *ThinBroker) Subscriptionv2Context(w rest.ResponseWriter, r *rest.Reque INFO.Printf("NEW subscription: %v\n", subReqv2) if r.Header.Get("User-Agent") == "lightweight-iot-broker" { - subReqv2.Subscriber.IsInternal = true - } else { - subReqv2.Subscriber.IsInternal = false - } + subReqv2.Subscriber.IsInternal = true + } else { + subReqv2.Subscriber.IsInternal = false + } tb.v2subscriptions_lock.Lock() ctxEle := &subReqv2 @@ -1610,3 +1654,1987 @@ func (tb *ThinBroker) removeFiwareHeadersFromId(ctxElem *ContextElement, fiwareS cutStr = strings.ReplaceAll(cutStr, "/", "~") ctxElem.Entity.ID = strings.TrimRight(ctxElem.Entity.ID, cutStr) } + +// NGSI-LD starts from here. + +// Create an NGSI-LD Entity +func (tb *ThinBroker) LDCreateEntity(w rest.ResponseWriter, r *rest.Request) { + //Also allow the header to json+ld for specific cases + if ctype, accept := r.Header.Get("Content-Type"), r.Header.Get("Accept"); (ctype == "application/json" || ctype == "application/ld+json") && accept == "application/ld+json" { + var context []interface{} + contextInPayload := true + //Get Link header if present + if link := r.Header.Get("Link"); link != "" { + contextInPayload = false // Context in Link header + linkMap := tb.extractLinkHeaderFields(link) // Keys in returned map are: "link", "rel" and "type" + if linkMap["rel"] != DEFAULT_CONTEXT { + context = append(context, linkMap["rel"]) // Make use of "link" and "type" also + } + } + context = append(context, DEFAULT_CONTEXT) + + //Get a resolved object ([]interface object) + resolved, err := tb.ExpandPayload(r, context, contextInPayload) + if err != nil { + + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + if err.Error() == "AlreadyExists!" { + rest.Error(w, "AlreadyExists!", 409) + return + } + if err.Error() == "Id can not be nil!" { + rest.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err.Error() == "Type can not be nil!" { + rest.Error(w, err.Error(), http.StatusBadRequest) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + sz := Serializer{} + + // Deserialize the payload here. + deSerializedEntity, err := sz.DeSerializeEntity(resolved) + + if err != nil { + rest.Error(w, err.Error(), 400) + return + } else { + //Update createdAt value. + deSerializedEntity["createdAt"] = time.Now().String() + /*for k, _ := range deSerializedEntity { // considering properties and relationships as attributes + if k != "id" && k != "type" && k != "modifiedAt" && k != "createdAt" && k != "observationSpace" && k != "operationSpace" && k != "location" && k != "@context" { + attrMap := deSerializedEntity[k].(map[string]interface{}) + attrMap["createdAt"] = time.Now().String() + } + }*/ + + // Store Context + + deSerializedEntity["@context"] = context + + if !strings.HasPrefix(deSerializedEntity["id"].(string),"urn:ngsi-ld:") { + rest.Error(w, "Entity id must contain uri!", 400) + return + } + w.Header().Set("Location","/ngis-ld/v1/entities/"+deSerializedEntity["id"].(string)) + w.WriteHeader(201) + + // Add the resolved entity to tb.ldEntities + tb.saveEntity(deSerializedEntity) + + //Register new context element on discovery + tb.registerLDContextElement(deSerializedEntity) + + //tb.LDNotifySubscribers(&deSerializedEntity, true) + } + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +func (tb *ThinBroker) updateLDspecificAttributeValues2RemoteSite(req map[string]interface{}, remoteURL string, eid string, attr string) (error, int) { + client := NGSI10Client{IoTBrokerURL: remoteURL, SecurityCfg: tb.SecurityCfg} + err, code := client.UpdateLDEntityspecificAttributeOnRemote(req, eid, attr) + + if err != nil { + return err, code + } + return nil, code +} + +func (tb *ThinBroker) updateLDAttributeValues2RemoteSite(req map[string]interface{}, remoteURL string, eid string) (error, int) { + client := NGSI10Client{IoTBrokerURL: remoteURL, SecurityCfg: tb.SecurityCfg} + err, code := client.UpdateLDEntityAttributeOnRemote(req, eid) + + if err != nil { + return err, code + } + return nil, code +} + +func (tb *ThinBroker) updateLDAttribute2RemoteSite(req map[string]interface{}, remoteURL string, eid string) error { + client := NGSI10Client{IoTBrokerURL: remoteURL, SecurityCfg: tb.SecurityCfg} + err := client.AppendLDEntityOnRemote(req, eid) + + if err != nil { + return err + } + return nil +} + +func (tb *ThinBroker) updateLDContextElement2RemoteSite(req map[string]interface{}, remoteURL string, link string) error { + client := NGSI10Client{IoTBrokerURL: remoteURL, SecurityCfg: tb.SecurityCfg} + err := client.CreateLDEntityOnRemote(req, link) + + if err != nil { + return err + } + return nil +} + +// Register a new context entity on Discovery +func (tb *ThinBroker) registerLDContextElement(elem map[string]interface{}) { + registerCtxReq := RegisterContextRequest{} + + entities := make([]EntityId, 0) + entityId := EntityId{} + entityId.ID = elem["id"].(string) + entityId.Type = elem["type"].(string) + entities = append(entities, entityId) + + ctxRegistrations := make([]ContextRegistration, 0) + + ctxReg := ContextRegistration{} + ctxReg.EntityIdList = entities + ctxRegAttr := ContextRegistrationAttribute{} + ctxRegAttrs := make([]ContextRegistrationAttribute, 0) + for k, attr := range elem { // considering properties and relationships as attributes + if k != "id" && k != "type" && k != "modifiedAt" && k != "createdAt" && k != "observationSpace" && k != "operationSpace" && k != "location" && k != "@context" { + attrValue := attr.(map[string]interface{}) + ctxRegAttr.Name = k + typ := attrValue["type"].(string) + if strings.Contains(typ, "Property") || strings.Contains(typ, "property") { + ctxRegAttr.Type = "Property" + } else if strings.Contains(typ, "Relationship") || strings.Contains(typ, "relationship") { + ctxRegAttr.Type = "Relationship" + } + ctxRegAttrs = append(ctxRegAttrs, ctxRegAttr) + } + } + ctxReg.ContextRegistrationAttributes = ctxRegAttrs + ctxReg.ProvidingApplication = tb.MyURL + + ctxRegistrations = append(ctxRegistrations, ctxReg) + + registerCtxReq.ContextRegistrations = ctxRegistrations + + // Send the registration to discovery + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + _, err := client.RegisterContext(®isterCtxReq) + if err != nil { + ERROR.Println(err) + } +} + +// Store the NGSI-LD Entities at local broker +func (tb *ThinBroker) saveEntity(ctxElem map[string]interface{}) { + eid := ctxElem["id"].(string) + tb.ldEntities_lock.Lock() + tb.ldEntities[eid] = ctxElem + tb.ldEntities_lock.Unlock() +} + +// GET API method for entity +func (tb *ThinBroker) ldGetEntity(eid string) interface{} { + tb.ldEntities_lock.RLock() + if entity := tb.ldEntities[eid]; entity != nil { + tb.ldEntities_lock.RUnlock() + compactEntity := tb.createOriginalPayload(entity) + return compactEntity + } else { + tb.ldEntities_lock.RUnlock() + return nil + } +} + +// Creating original payload as provided by user from FogFlow Data Structure +func (tb *ThinBroker) createOriginalPayload(entity interface{}) interface{} { + entityMap := entity.(map[string]interface{}) + + // Expanding the entity to get uniformly expanded entity which was missing in internal representation + expandedEntity, err := tb.ExpandData(entityMap) + + if err != nil { + DEBUG.Println("Error while expanding:", err) + return nil + } + + // Compacting the expanded entity. + entity1 := expandedEntity[0].(map[string]interface{}) + compactEntity, err := tb.compactData(entity1, entityMap["@context"]) + if err != nil { + DEBUG.Println("Error while compacting:", err) + return nil + } + return compactEntity +} + +// Compacting data to display to user in original form. +func (tb *ThinBroker) compactData(entity map[string]interface{}, context interface{}) (interface{}, error) { + proc := ld.NewJsonLdProcessor() + options := ld.NewJsonLdOptions("") + compacted, err := proc.Compact(entity, context, options) + return compacted, err +} + +func (tb *ThinBroker) RegisterCSource(w rest.ResponseWriter, r *rest.Request) { + var context []interface{} + //Also allow the header to json+ld for specific cases + if ctype, accept := r.Header.Get("Content-Type"), r.Header.Get("Accept"); (ctype == "application/json" || ctype == "application/ld+json") && accept == "application/ld+json" { + contextInPayload := true + //Get Link header if present + if link := r.Header.Get("Link"); link != "" { + contextInPayload = false // Context in Link header + linkMap := tb.extractLinkHeaderFields(link) // Keys in returned map are: "link", "rel" and "type" + if linkMap["rel"] != DEFAULT_CONTEXT { + context = append(context, linkMap["rel"]) // Make use of "link" and "type" also + } + } + context = append(context, DEFAULT_CONTEXT) + // Get an []interface object + resolved, err := tb.ExpandPayload(r, context, contextInPayload) + + if err != nil { + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + if err.Error() == "Type can not be nil!" { + rest.Error(w, err.Error(), http.StatusBadRequest) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + sz := Serializer{} + // Serialize payload + deSerializedRegistration, err := sz.DeSerializeRegistration(resolved) + + // IDPattern check + for _, info := range deSerializedRegistration.Information { + for _, entity := range info.Entities { + if entity.IdPattern != "" { + rest.Error(w, "Registration with Entity IdPattern is not supported!", 400) + return + } + } + } + + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + deSerializedRegistration.CreatedAt = time.Now().String() + err := tb.saveLDRegistrationInMemory(deSerializedRegistration) + if err != nil { + rest.Error(w, err.Error(), 409) + return + } + rid, err := tb.sendLDRegistrationToDiscovery(deSerializedRegistration) + + // Send out the response + if err != nil { + w.WriteJson(err) + } else { + w.WriteHeader(201) + w.WriteJson(rid) + } + } + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +func (tb *ThinBroker) saveLDRegistrationInMemory(reg CSourceRegistrationRequest) error { + rid := reg.Id + var eid string + //Confirm if registration is pre-existing. + tb.ldEntityID2RegistrationID_lock.Lock() + for _, info := range reg.Information { + for _, entity := range info.Entities { + if entity.ID != "" { + eid = entity.ID + } else if entity.IdPattern != "" { + eid = entity.IdPattern + } + if eid != "" { + if _, ok := tb.ldEntityID2RegistrationID[eid]; ok == true { + tb.ldEntityID2RegistrationID_lock.Unlock() + err := errors.New("Entity is already registered!") + return err + } + } + } + } + tb.ldEntityID2RegistrationID_lock.Unlock() + + // Insert the entity into tb.ldContextRegistrations + tb.ldContextRegistrations_lock.Lock() + + tb.ldContextRegistrations[rid] = reg + tb.ldContextRegistrations_lock.Unlock() + + for _, info := range reg.Information { + for _, entity := range info.Entities { + if entity.ID != "" { + eid = entity.ID + } else if entity.IdPattern != "" { + eid = entity.IdPattern + } + if eid != "" { + tb.saveEntityId2RegistrationIdMapping(eid, rid) + } + } + } + return nil +} + +func (tb *ThinBroker) saveEntityId2RegistrationIdMapping(eid string, rid string) { + tb.ldEntityID2RegistrationID_lock.Lock() + tb.ldEntityID2RegistrationID[eid] = rid + + tb.ldEntityID2RegistrationID_lock.Unlock() +} + +func (tb *ThinBroker) sendLDRegistrationToDiscovery(reg CSourceRegistrationRequest) (string, error) { + registerCtxReq := RegisterContextRequest{} + if reg.Id != "" { + registerCtxReq.RegistrationId = reg.Id + } + ctxRegistrations := make([]ContextRegistration, 0) + + for _, regInfo := range reg.Information { + ctxRegAttrs := make([]ContextRegistrationAttribute, 0) + ctxRegAttr := ContextRegistrationAttribute{} + for _, property := range regInfo.Properties { + ctxRegAttr.Name = property + ctxRegAttr.Type = PROPERTY + ctxRegAttrs = append(ctxRegAttrs, ctxRegAttr) + } + for _, relationship := range regInfo.Relationships { + ctxRegAttr.Name = relationship + ctxRegAttr.Type = RELATIONSHIP + ctxRegAttrs = append(ctxRegAttrs, ctxRegAttr) + } + + ctxReg := ContextRegistration{} + ctxReg.EntityIdList = regInfo.Entities + ctxReg.ContextRegistrationAttributes = ctxRegAttrs + // ctxReg.Metadata is nil + ctxReg.ProvidingApplication = tb.MyURL + ctxRegistrations = append(ctxRegistrations, ctxReg) + } + registerCtxReq.ContextRegistrations = ctxRegistrations + + // Send the registration to discovery + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + rid, err := client.RegisterContext(®isterCtxReq) + if err != nil { + ERROR.Println(err) + return "", err + } + return rid, nil +} + +func (tb *ThinBroker) LDCreateSubscription(w rest.ResponseWriter, r *rest.Request) { + var context []interface{} + context = append(context, DEFAULT_CONTEXT) + //Also allow the header to json+ld for specific cases + if ctype := r.Header.Get("Content-Type"); ctype == "application/json" || ctype == "application/ld+json" { + contextInPayload := true + //Get Link header if present + if link := r.Header.Get("Link"); link != "" { + contextInPayload = false // Context in Link header + linkMap := tb.extractLinkHeaderFields(link) // Keys in returned map are: "link", "rel" and "type" + if linkMap["rel"] != DEFAULT_CONTEXT { + context = append(context, linkMap["rel"]) // Make use of "link" and "type" also + } + } + + // Get an []interface object + resolved, err := tb.ExpandPayload(r, context, contextInPayload) + + if err != nil { + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + if err.Error() == "Type can not be nil!" { + rest.Error(w, err.Error(), http.StatusBadRequest) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + sz := Serializer{} + deSerializedSubscription, err := sz.DeSerializeSubscription(resolved) + + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + deSerializedSubscription.CreatedAt = time.Now().String() + // Create Subscription Id, if missing + + if deSerializedSubscription.Id == "" { + u1, err := uuid.NewV4() + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sid := u1.String() + deSerializedSubscription.Id = sid + + } + + deSerializedSubscription.Status = "active" // others allowed: paused, expired + deSerializedSubscription.Notification.Format = "normalized" // other allowed: keyValues + deSerializedSubscription.Subscriber.BrokerURL = tb.MyURL + tb.createEntityID2SubscriptionsIDMap(&deSerializedSubscription) + if err := tb.createSubscription(&deSerializedSubscription);err != nil { + rest.Error(w, "Already exist!", 409) + return + } + if err := tb.SubscribeLDContextAvailability(&deSerializedSubscription); err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusCreated) + //w.WriteJson(deSerializedSubscription.Id) + subResp := SubscribeContextResponse{} + subResp.SubscribeResponse.SubscriptionId = deSerializedSubscription.Id + subResp.SubscribeError.SubscriptionId = deSerializedSubscription.Id + w.WriteJson(&subResp) + } + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +// Subscribe to Discovery for context availabiltiy +func (tb *ThinBroker) SubscribeLDContextAvailability(subReq *LDSubscriptionRequest) error { + ctxAvailabilityRequest := SubscribeContextAvailabilityRequest{} + + for key, entity := range subReq.Entities { + if entity.IdPattern != "" { + entity.IsPattern = true + } + subReq.Entities[key] = entity + } + ctxAvailabilityRequest.Entities = subReq.Entities + ctxAvailabilityRequest.Attributes = subReq.WatchedAttributes + //copy(ctxAvailabilityRequest.Attributes, subReq.Notification.Attributes) + ctxAvailabilityRequest.Reference = tb.MyURL + "/notifyContextAvailability" + ctxAvailabilityRequest.Duration = subReq.Expires + + // Subscribe to discovery + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + AvailabilitySubID, err := client.SubscribeContextAvailability(&ctxAvailabilityRequest) + + if AvailabilitySubID != "" { + tb.createSubscriptionIdMappings(subReq.Id, AvailabilitySubID) + tb.subLinks_lock.Lock() + notifyMessage, alreadyBack := tb.tmpNGSILDNotifyCache[AvailabilitySubID] + tb.subLinks_lock.Unlock() + if alreadyBack == true { + INFO.Println("========forward the availability notify that arrived earlier===========") + tb.handleNGSI9Notify(subReq.Id, notifyMessage) + + tb.subLinks_lock.Lock() + delete(tb.tmpNGSILDNotifyCache, AvailabilitySubID) + tb.subLinks_lock.Unlock() + } + return nil + } else { + INFO.Println("failed to subscribe the availability of requested entities ", err) + return err + } +} + +// Store in EntityID - SubID Map +func (tb *ThinBroker) createEntityID2SubscriptionsIDMap(subReq *LDSubscriptionRequest) { + tb.e2sub_lock.Lock() + for _, entities := range subReq.Entities { + var eid string + if entities.IdPattern != "" { + eid = entities.IdPattern + } else if entities.ID != "" { + eid = entities.ID + } + tb.entityId2Subcriptions[eid] = append(tb.entityId2Subcriptions[eid], subReq.Id) + + } + tb.e2sub_lock.Unlock() +} + +// Store in SubID - SubscriptionPayload Map +func (tb *ThinBroker) createSubscription(subscription *LDSubscriptionRequest) (error){ + subscription.Subscriber.RequireReliability = true + subscription.Subscriber.LDNotifyCache = make([]map[string]interface{}, 0) + tb.ldSubscriptions_lock.Lock() + if _,exist := tb.ldSubscriptions[subscription.Id]; exist == true { + fmt.Println("Already exists here...!!") + tb.ldSubscriptions_lock.Unlock() + err := errors.New("AlreadyExists!") + fmt.Println("Error: ", err.Error()) + return err + }else { + tb.ldSubscriptions[subscription.Id] = subscription + tb.ldSubscriptions_lock.Unlock() + } + return nil +} + +// Store SubID - AvailabilitySubID Mappings +func (tb *ThinBroker) createSubscriptionIdMappings(subID string, availabilitySubID string) { + tb.subLinks_lock.Lock() + tb.main2Other[subID] = append(tb.main2Other[subID], availabilitySubID) + + tb.availabilitySub2MainSub[availabilitySubID] = subID + + tb.subLinks_lock.Unlock() +} + +// Expand the payload +func (tb *ThinBroker) ExpandPayload(r *rest.Request, context []interface{}, contextInPayload bool) ([]interface{}, error) { + //get map[string]interface{} of reqBody + itemsMap, err := tb.getStringInterfaceMap(r) + if err != nil { + return nil, err + } else { + // Check the type of payload: Entity, registration or Subscription + var payloadType string + if _, ok := itemsMap["type"]; ok == true { + payloadType = itemsMap["type"].(string) + } else if _, ok := itemsMap["@type"]; ok == true { + typ := itemsMap["@type"].([]interface{}) + payloadType = typ[0].(string) + } + if payloadType == "" { + err := errors.New("Type can not be nil!") + return nil, err + } + if payloadType != "ContextSourceRegistration" && payloadType != "Subscription" { + // Payload is of Entity Type + // Check if some other broker is registered for providing this entity or not + var entityId string + if _, ok := itemsMap["id"]; ok == true { + entityId = itemsMap["id"].(string) + } else if _, ok := itemsMap["@id"]; ok == true { + entityId = itemsMap["@id"].(string) + } + + if entityId == "" { + err := errors.New("Id can not be nil!") + return nil, err + } + ownerURL := tb.queryOwnerOfLDEntity(entityId) + if ownerURL == tb.MyURL { + tb.ldEntities_lock.RLock() + if _, ok := tb.ldEntities[entityId]; ok == true { + fmt.Println("Already exists here...!!") + tb.ldEntities_lock.RUnlock() + err := errors.New("AlreadyExists!") + fmt.Println("Error: ", err.Error()) + return nil, err + } + tb.ldEntities_lock.RUnlock() + } + if ownerURL != tb.MyURL { + ownerURL = strings.TrimSuffix(ownerURL, "/ngsi10") + link := r.Header.Get("Link") // Pick link header if present + fmt.Println("Here 1..., link sending to remote broker:", link, "\nOwner URL:", ownerURL, "\nMy URL:", tb.MyURL) + err := tb.updateLDContextElement2RemoteSite(itemsMap, ownerURL, link) + return nil, err + } + } + + // Update Context in itemMap + if contextInPayload == true && itemsMap["@context"] != nil { + contextItems := itemsMap["@context"].([]interface{}) + context = append(context, contextItems...) + } + itemsMap["@context"] = context + + if expanded, err := tb.ExpandData(itemsMap); err != nil { + return nil, err + } else { + + return expanded, nil + } + } +} + +func (tb *ThinBroker) ExpandAttributePayload(r *rest.Request, context []interface{}, params ...string) ([]interface{}, error) { + //eid := params[0] + itemsMap, err := tb.getStringInterfaceMap(r) + context = append(context, DEFAULT_CONTEXT) + //get map[string]interface{} of reqBody + //itemsMap, err := tb.getStringInterfaceMap(r) + if err != nil { + return nil, err + } else { + // Update Context in itemMap + if itemsMap["@context"] != nil { + contextItem := itemsMap["@context"] + context = append(context, contextItem) + } + itemsMap["@context"] = context + + //Add attribute to payload, if found in params, case: Partial update + if params != nil { + eid := params[0] + attrName := params[1] + + tb.ldEntities_lock.Lock() + // Check if the attribute exists + entity := tb.ldEntities[eid] + entityMap := entity.(map[string]interface{}) + attrFound := false + attrType := "" // To record whether it is Property or Relationship + for attr, attrVal := range entityMap { + if strings.HasSuffix(attr, "/"+attrName) { + attrFound = true + // Check the type of attribute (Property or Relationship) + attrMp := attrVal.(map[string]interface{}) + attrType = attrMp["type"].(string) + // Prepare attribute payload from partial payload + mp := make(map[string]interface{}) + for key, val := range itemsMap { + switch key { + case "@context": + continue + default: + mp[key] = val + delete(itemsMap, key) + } + } + mp["type"] = attrType + itemsMap[attrName] = mp + break + } + } + if attrFound != true { + tb.ldEntities_lock.Unlock() + err := errors.New("Attribute not found!") + return nil, err + } + tb.ldEntities_lock.Unlock() + } + if expanded, err := tb.ExpandData(itemsMap); err != nil { + return nil, err + } else { + return expanded, nil + } + } +} + +func (tb *ThinBroker) getTypeResolved(link string, typ string) string { + linkMap := tb.extractLinkHeaderFields(link) // Keys in returned map are: "link", "rel" and "type" + var context []interface{} + context = append(context, linkMap["rel"]) + + itemsMap := make(map[string]interface{}) + itemsMap["@context"] = context + itemsMap["type"] = typ //Error, when entire slice typ is assigned : invalid type value: @type value must be a string or array of strings + resolved, err := tb.ExpandData(itemsMap) + + if err != nil { + DEBUG.Println("Error: ", err) + return "" + } + + sz := Serializer{} + typ = sz.DeSerializeType(resolved) + return typ +} + +// Expand the NGSI-LD Data with context +func (tb *ThinBroker) ExpandData(v interface{}) ([]interface{}, error) { + proc := ld.NewJsonLdProcessor() + options := ld.NewJsonLdOptions("") + //LD processor expands the data and returns []interface{} + expanded, err := proc.Expand(v, options) + return expanded, err +} + +//Get string-interface{} map from request body +func (tb *ThinBroker) getStringInterfaceMap(r *rest.Request) (map[string]interface{}, error) { + // Get bite array of request body + reqBytes, err := ioutil.ReadAll(r.Body) + + if err != nil { + return nil, err + } + // Unmarshal using a generic interface + var req interface{} + err = json.Unmarshal(reqBytes, &req) + if err != nil { + DEBUG.Println("Invalid Request.") + return nil, err + } + // Parse the JSON object into a map with string keys + itemsMap := req.(map[string]interface{}) + + if len(itemsMap) != 0 { + return itemsMap, nil + } else { + return nil, errors.New("EmptyPayload!") + } +} + +func (tb *ThinBroker) extractLinkHeaderFields(link string) map[string]string { + mp := make(map[string]string) + linkArray := strings.Split(link, ";") + + for i, arrValue := range linkArray { + linkArray[i] = strings.Trim(arrValue, " ") + if strings.HasPrefix(arrValue, "<{{link}}>") { + continue // TBD, context link + } else if strings.HasPrefix(arrValue, "http") { + mp["link"] = arrValue + } else if strings.HasPrefix(arrValue, " rel=") { + mp["rel"] = arrValue[6 : len(arrValue)-1] // Trimmed `rel="` and `"` + } else if strings.HasPrefix(arrValue, " type=") { + mp["type"] = arrValue[7 : len(arrValue)-1] // Trimmed `type="` and `"` + } + } + + return mp +} + +func (tb *ThinBroker) queryOwnerOfLDEntity(eid string) string { + inLocalBroker := true + + tb.ldEntities_lock.RLock() + _, exist := tb.ldEntities[eid] + inLocalBroker = exist + tb.ldEntities_lock.RUnlock() + + if inLocalBroker == true { + return tb.myProfile.MyURL + } else { + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + brokerURL, _ := client.GetProviderURL(eid) + if brokerURL == "" { + return tb.myProfile.MyURL + } + return brokerURL + } +} + +func (tb *ThinBroker) LDNotifySubscribers(ctxElem map[string]interface{}, checkSelectedAttributes bool) { + eid := ctxElem["id"].(string) + tb.e2sub_lock.RLock() + defer tb.e2sub_lock.RUnlock() + var subscriberList []string + if list, ok := tb.entityId2Subcriptions[eid]; ok == true { + subscriberList = append(subscriberList, list...) + } + for k, _ := range tb.entityId2Subcriptions { + matched := tb.matchPattern(k, eid) // (pattern, id) to check if the current eid lies in the pattern given in the key. + if matched == true { + list := tb.entityId2Subcriptions[k] + subscriberList = append(subscriberList, list...) + } + } + //send this context element to the subscriber + for _, sid := range subscriberList { + elements := make([]map[string]interface{}, 0) + + if checkSelectedAttributes == true { + selectedAttributes := make([]string, 0) + + tb.ldSubscriptions_lock.RLock() + + if subscription, exist := tb.ldSubscriptions[sid]; exist { + if subscription.Notification.Attributes != nil { + selectedAttributes = append(selectedAttributes, subscription.Notification.Attributes...) + } + } + tb.ldSubscriptions_lock.RUnlock() + tb.ldEntities_lock.RLock() + //element := tb.ldEntities[eid].CloneWithSelectedAttributes(selectedAttributes) + element := tb.ldEntities[eid] + tb.ldEntities_lock.RUnlock() + elementMap := element.(map[string]interface{}) + elements = append(elements, elementMap) + } else { + elements = append(elements, ctxElem) + } + go tb.sendReliableNotifyToNgsiLDSubscriber(elements, sid) + } +} + +func (tb *ThinBroker) notifyOneSubscriberWithCurrentStatusOfLD(entities []EntityId, sid string, selectedAttributes []string) { + // Create NGSI-LD Context Element + elements := make([]map[string]interface{}, 0) + tb.ldEntities_lock.Lock() + for _, entity := range entities { + if element, exist := tb.ldEntities[entity.ID]; exist { + elementMap := element.(map[string]interface{}) + returnedElement := ldCloneWithSelectedAttributes(elementMap, selectedAttributes) + elements = append(elements, returnedElement) + } + } + tb.ldEntities_lock.Unlock() + go tb.sendReliableNotifyToNgsiLDSubscriber(elements, sid) +} + +func (tb *ThinBroker) sendReliableNotifyToNgsiLDSubscriber(elements []map[string]interface{}, sid string) { + tb.ldSubscriptions_lock.Lock() + ldSubscription, ok := tb.ldSubscriptions[sid] + if ok == false { + tb.ldSubscriptions_lock.Unlock() + } + subscriberURL := ldSubscription.Notification.Endpoint.URI + if ldSubscription.Subscriber.RequireReliability == true && len(ldSubscription.Subscriber.LDNotifyCache) > 0 { + DEBUG.Println("resend notify: ", len(ldSubscription.Subscriber.LDNotifyCache)) + elements = append(elements, ldSubscription.Subscriber.LDNotifyCache...) + ldSubscription.Subscriber.LDNotifyCache = make([]map[string]interface{}, 0) + } + tb.ldSubscriptions_lock.Unlock() + err := ldPostNotifyContext(elements, sid, subscriberURL /* true, */, tb.SecurityCfg) + notifyTime := time.Now().String() + if err != nil { + INFO.Println("NOTIFY is not received by the subscriber, ", subscriberURL) + + tb.ldSubscriptions_lock.Lock() + if ldSubscription, exist := tb.ldSubscriptions[sid]; exist { + if ldSubscription.Subscriber.RequireReliability == true { + ldSubscription.Subscriber.LDNotifyCache = append(ldSubscription.Subscriber.LDNotifyCache, elements...) + ldSubscription.Notification.LastFailure = notifyTime + ldSubscription.Notification.Status = "failed" + tb.tmpNGSIldNotifyCache = append(tb.tmpNGSIldNotifyCache, sid) + } + } + tb.ldSubscriptions_lock.Unlock() + return + } + tb.updateLastSuccessParameters(notifyTime, sid) + INFO.Println("NOTIFY is sent to the subscriber, ", subscriberURL) +} + +func (tb *ThinBroker) updateLastSuccessParameters(time string, sid string) { + tb.ldSubscriptions_lock.Lock() + if ldSubscription, exist := tb.ldSubscriptions[sid]; exist { + ldSubscription.Notification.LastNotification = time + ldSubscription.Notification.LastSuccess = time + ldSubscription.Notification.TimeSent += 1 + ldSubscription.Notification.Status = "ok" + } + tb.ldSubscriptions_lock.Unlock() +} + +//PATCH +func (tb *ThinBroker) LDUpdateEntityAttributes(w rest.ResponseWriter, r *rest.Request) { + var context []interface{} + eid := r.PathParam("eid") + if ctype := r.Header.Get("Content-Type"); ctype == "application/json" || ctype == "application/ld+json" { + tb.ldEntities_lock.RLock() + if _, ok := tb.ldEntities[eid]; ok == true { + tb.ldEntities_lock.RUnlock() + //Get a resolved object ([]interface object) + resolved, err := tb.ExpandAttributePayload(r, context) + if err != nil { + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + // Deserialize the resolved payload + sz := Serializer{} + deSerializedAttributePayload, err := sz.DeSerializeEntity(resolved) + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + err := tb.updateAttributes(deSerializedAttributePayload, eid) + if err != nil { + rest.Error(w, err.Error(), 207) + return + } + w.WriteHeader(204) + } + } + } else { + tb.ldEntities_lock.RUnlock() + ownerURL := tb.queryOwnerOfLDEntity(eid) + if ownerURL != tb.MyURL { + ownerURL = strings.TrimSuffix(ownerURL, "/ngsi10") + reqCxt, _ := tb.getStringInterfaceMap(r) + //link := r.Header.Get("Link") // Pick link header if present + //fmt.Println("Here 1..., link sending to remote broker:", link, "\nOwner URL:", ownerURL, "\nMy URL:", tb.MyURL) + _, code := tb.updateLDAttributeValues2RemoteSite(reqCxt, ownerURL, eid) + if code == 207 { + //rest.Error(w, err.Error(), 404) + //ERROR.Println(err) + rest.Error(w, "The attribute was not found!", 404) + return + } + w.WriteHeader(204) + + //return nil, err + } else { + ERROR.Println("The entity was not found!") + rest.Error(w, "The entity was not found!", 404) + return + } + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +//POST +func (tb *ThinBroker) LDAppendEntityAttributes(w rest.ResponseWriter, r *rest.Request) { + var context []interface{} + eid := r.PathParam("eid") + if ctype := r.Header.Get("Content-Type"); ctype == "application/json" || ctype == "application/ld+json" { + tb.ldEntities_lock.RLock() + if _, ok := tb.ldEntities[eid]; ok == true { + tb.ldEntities_lock.RUnlock() + //Get a resolved object ([]interface object) + resolved, err := tb.ExpandAttributePayload(r, context) + + if err != nil { + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + // Deserialize the resolved payload + sz := Serializer{} + deSerializedAttributePayload, err := sz.DeSerializeEntity(resolved) + deSerializedAttributePayload["@context"] = context + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + //Update createdAt for each new attribute + for key, _ := range deSerializedAttributePayload { + if key != "@context" && key != "modifiedAt" { + attr := deSerializedAttributePayload[key].(map[string]interface{}) + attr["createdAt"] = time.Now().String() + deSerializedAttributePayload[key] = attr + } + } + + // Write entity to tb.ldEntities + tb.ldEntities_lock.Lock() + entity := tb.ldEntities[eid] + entityMap := entity.(map[string]interface{}) + multiStatus := false + for k, attr := range deSerializedAttributePayload { + if k != "@context" && k != "modifiedAt" { + if _, ok := entityMap[k]; ok == true { + multiStatus = true // atleast one duplicate attribute found + } else { + entityMap[k] = attr + } + } + } + entityMap["modifiedAt"] = time.Now().String() + + // Update context in entity in tb.ldEntities + ctxList := entityMap["@context"].([]interface{}) + ctxList = append(ctxList, context...) + entityMap["@context"] = ctxList + + tb.ldEntities[eid] = entityMap + + tb.registerLDContextElement(entityMap) + tb.ldEntities_lock.Unlock() + + // Update Registration on Broker + tb.appendLDAttributes(deSerializedAttributePayload, eid) + if multiStatus == true { + rest.Error(w, "Some duplicate attributes were found!", 207) + } else { + w.WriteHeader(204) + } + } + } + } else { + tb.ldEntities_lock.RUnlock() + ownerURL := tb.queryOwnerOfLDEntity(eid) + if ownerURL != tb.MyURL { + ownerURL = strings.TrimSuffix(ownerURL, "/ngsi10") + reqCxt, _ := tb.getStringInterfaceMap(r) + //link := r.Header.Get("Link") // Pick link header if present + //fmt.Println("Here 1..., link sending to remote broker:", link, "\nOwner URL:", ownerURL, "\nMy URL:", tb.MyURL) + tb.updateLDAttribute2RemoteSite(reqCxt, ownerURL, eid) + w.WriteHeader(204) + //return nil, err + } else { + + rest.Error(w, "The entity was not found!", 404) + return + } + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +func (tb *ThinBroker) appendLDAttributes(elem map[string]interface{}, eid string) { + tb.ldEntityID2RegistrationID_lock.Lock() + if rid, ok := tb.ldEntityID2RegistrationID[eid]; ok == true { + tb.ldEntityID2RegistrationID_lock.Unlock() + + tb.ldContextRegistrations_lock.Lock() + for k, info := range tb.ldContextRegistrations[rid].Information { + for _, entity := range info.Entities { + if entity.ID == eid { + for key, attr := range elem { + if key != "@context" { + attrValue := attr.(map[string]interface{}) + if strings.Contains(attrValue["@type"].(string), "Property") { + for _, existingProperty := range tb.ldContextRegistrations[rid].Information[k].Properties { + if existingProperty == key { + continue + } + tb.ldContextRegistrations[rid].Information[k].Properties = append(tb.ldContextRegistrations[rid].Information[k].Properties, key) + } + } else if strings.Contains(attrValue["@type"].(string), "Relationship") { + for _, existingRelationship := range tb.ldContextRegistrations[rid].Information[k].Relationships { + if existingRelationship == key { + continue + } + tb.ldContextRegistrations[rid].Information[k].Relationships = append(tb.ldContextRegistrations[rid].Information[k].Relationships, key) + } + } + } + } + } + } + } + tb.ldContextRegistrations_lock.Unlock() + } else { + tb.ldEntityID2RegistrationID_lock.Unlock() + } +} + +//PATCH: Partial update, Attr name in URL, value in payload +func (tb *ThinBroker) LDUpdateEntityByAttribute(w rest.ResponseWriter, r *rest.Request) { + var context []interface{} + eid := r.PathParam("eid") + attr := r.PathParam("attr") + if ctype := r.Header.Get("Content-Type"); ctype == "application/json" || ctype == "application/ld+json" { + tb.ldEntities_lock.RLock() + if _, ok := tb.ldEntities[eid]; ok == true { + tb.ldEntities_lock.RUnlock() + //Get a resolved object ([]interface object) + resolved, err := tb.ExpandAttributePayload(r, context, eid, attr) + + if err != nil { + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + if err.Error() == "Attribute not found!" { + rest.Error(w, "Attribute not found!", 404) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + // Deserialize the resolved payload + sz := Serializer{} + deSerializedAttributePayload, err := sz.DeSerializeEntity(resolved) + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + tb.updateAttributes(deSerializedAttributePayload, eid) + w.WriteHeader(204) + } + } + } else { + tb.ldEntities_lock.RUnlock() + ownerURL := tb.queryOwnerOfLDEntity(eid) + if ownerURL != tb.MyURL { + ownerURL = strings.TrimSuffix(ownerURL, "/ngsi10") + reqCxt, _ := tb.getStringInterfaceMap(r) + //link := r.Header.Get("Link") // Pick link header if present + //fmt.Println("Here 1..., link sending to remote broker:", link, "\nOwner URL:", ownerURL, "\nMy URL:", tb.MyURL) + _, code := tb.updateLDspecificAttributeValues2RemoteSite(reqCxt, ownerURL, eid, attr) + if code == 404 { + + rest.Error(w, "The attribute was not found!", 404) + return + } + w.WriteHeader(204) + //return nil, err + } else { + + ERROR.Println("The entity was not found!") + rest.Error(w, "The entity was not found!", 404) + return + } + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +func (tb *ThinBroker) updateAttributes(elem map[string]interface{}, eid string) error { + tb.ldEntities_lock.Lock() + entity := tb.ldEntities[eid] + entityMap := entity.(map[string]interface{}) + missing := false + for k, _ := range elem { + if k != "@context" && k != "modifiedAt" { + if _, ok := entityMap[k]; ok == true { + entityAttrMap := entityMap[k].(map[string]interface{}) // existing + attrMap := elem[k].(map[string]interface{}) // to be updated as + if strings.Contains(attrMap["type"].(string), "Property") { + if attrMap["value"] != nil { + entityAttrMap["value"] = attrMap["value"] + } + if attrMap["observedAt"] != nil { + entityAttrMap["observedAt"] = attrMap["observedAt"] + } + if attrMap["datasetId"] != nil { + entityAttrMap["datasetId"] = attrMap["datasetId"] + } + if attrMap["instanceId"] != nil { + entityAttrMap["instanceId"] = attrMap["instanceId"] + } + if attrMap["unitCode"] != nil { + entityAttrMap["unitCode"] = attrMap["unitCode"] + } + } else if strings.Contains(attrMap["type"].(string), "Relationship") { + if attrMap["object"] != nil { + entityAttrMap["object"] = attrMap["object"] + } + if attrMap["providedBy"] != nil { + entityAttrMap["providedBy"] = attrMap["providedBy"] + } + if attrMap["datasetId"] != nil { + entityAttrMap["datasetId"] = attrMap["datasetId"] + } + if attrMap["instanceId"] != nil { + entityAttrMap["instanceId"] = attrMap["instanceId"] + } + } + entityAttrMap["modifiedAt"] = time.Now().String() + entityMap[k] = entityAttrMap + } else { + missing = true + ERROR.Println("Attribute", k, "was not found in the entity!") + } + } + } + entityMap["modifiedAt"] = time.Now().String() + tb.ldEntities[eid] = entityMap + + // registration of entity is not required on discovery while attribute updation + //tb.registerLDContextElement(entityMap) + + // send notification to the subscriber + + go tb.LDNotifySubscribers(entityMap, true) + + tb.ldEntities_lock.Unlock() + + if missing == true { + err := errors.New("Some attributes were not found!") + return err + } + return nil +} + +func (tb *ThinBroker) ldDeleteEntity(eid string) error { + tb.ldEntities_lock.Lock() + if tb.ldEntities[eid] != nil { + delete(tb.ldEntities, eid) + } else { + tb.ldEntities_lock.Unlock() + ERROR.Println("Entity not found!") + err := errors.New("Entity not found!") + return err + } + + // Delete registration from Broker + tb.ldEntityID2RegistrationID_lock.Lock() + tb.ldContextRegistrations_lock.Lock() + + rid := tb.ldEntityID2RegistrationID[eid] + delete(tb.ldContextRegistrations, rid) + delete(tb.ldEntityID2RegistrationID, eid) + + tb.ldContextRegistrations_lock.Unlock() + tb.ldEntityID2RegistrationID_lock.Unlock() + + // Unregister entity from Discovery + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + err := client.UnregisterEntity(eid) + if err != nil { + ERROR.Println(err) + } + + tb.ldEntities_lock.Unlock() + return nil +} + +func (tb *ThinBroker) LDDeleteEntityAttribute(w rest.ResponseWriter, r *rest.Request) { + var req interface{} + var eid = r.PathParam("eid") + var attr = r.PathParam("attr") + + if ctype := r.Header.Get("Content-Type"); ctype == "application/json" || ctype == "application/ld+json" { + reqBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + rest.Error(w, err.Error(), 400) + } + // Unmarshal using a generic interface + err = json.Unmarshal(reqBytes, &req) + if err != nil { + rest.Error(w, err.Error(), 400) + } + } + + err := tb.ldDeleteEntityAttribute(eid, attr, req) + + if err == nil { + w.WriteHeader(204) + } else { + rest.Error(w, err.Error(), 404) + } +} + +func (tb *ThinBroker) ldDeleteEntityAttribute(eid string, attr string, req interface{}) error { + tb.ldEntities_lock.Lock() + if tb.ldEntities[eid] != nil { + entityMap := tb.ldEntities[eid].(map[string]interface{}) + + attrExists := false + //for i := 0; i < len(tb.ldEntities[eid].Properties); i++ { + for attrN, _ := range entityMap { + if strings.HasSuffix(attrN, "/"+attr) { + attrExists = true + + delete(entityMap, attrN) + tb.ldEntities[eid] = entityMap + + } + } + if attrExists == false { + tb.ldEntities_lock.Unlock() + ERROR.Println("Attribute not found!") + err := errors.New("Attribute not found!") + return err + } + // Deleting attribute from registration at Broker: Get rid at broker + rid := "" + tb.ldEntityID2RegistrationID_lock.RLock() + if _, ok := tb.ldEntityID2RegistrationID[eid]; ok == true { + rid = tb.ldEntityID2RegistrationID[eid] + + } + tb.ldEntityID2RegistrationID_lock.RUnlock() + + // Deleting attribute from registration at Broker: Update registration at broker, if found + if rid != "" { // Registration is present at Broker; for registrations created explicitly at FogFlow. + tb.ldContextRegistrations_lock.Lock() + attrType := "" + // update registration at broker here. + for k, info := range tb.ldContextRegistrations[rid].Information { + for _, entity := range info.Entities { + if entity.ID == eid { + if strings.Contains(attrType, "Property") { + for key, property := range tb.ldContextRegistrations[rid].Information[k].Properties { + if property == attr { + tb.ldContextRegistrations[rid].Information[k].Properties = append(tb.ldContextRegistrations[rid].Information[k].Properties[:key], tb.ldContextRegistrations[rid].Information[k].Properties[key+1:]...) + break + } + } + } else if strings.Contains(attrType, "Relationship") { + for key, relationship := range tb.ldContextRegistrations[rid].Information[k].Relationships { + if relationship == attr { + tb.ldContextRegistrations[rid].Information[k].Relationships = append(tb.ldContextRegistrations[rid].Information[k].Relationships[:key], tb.ldContextRegistrations[rid].Information[k].Relationships[key+1:]...) + break + } + } + } + } + } + } + tb.ldContextRegistrations_lock.Unlock() + } + // Update Registration at Discovery + + tb.registerLDContextElement(entityMap) + + } else { + tb.ldEntities_lock.Unlock() + + ERROR.Println("Entity not found!") + err := errors.New("Entity not found!") + return err + + } + tb.ldEntities_lock.Unlock() + // } + /*else { + tb.ldEntities_lock.Unlock() + ERROR.Println("Entity not found!") + err := errors.New("Entity not found!") + return err + }*/ + // } + // } + return nil +} + +func (tb *ThinBroker) ldEntityGetByAttribute(attrs []string) []interface{} { + var entities []interface{} + tb.ldEntities_lock.Lock() + for _, entity := range tb.ldEntities { + entityMap := entity.(map[string]interface{}) + allExist := true + for _, attr := range attrs { + if _, ok := entityMap[attr]; ok != true { + allExist = false + } + } + if allExist == true { + compactEntity := tb.createOriginalPayload(entity) + entities = append(entities, compactEntity) + } + } + tb.ldEntities_lock.Unlock() + return entities +} + +func (tb *ThinBroker) ldEntityGetById(eids []string, typ []string) []interface{} { + tb.ldEntities_lock.Lock() + var entities []interface{} + + for index, eid := range eids { + if entity, ok := tb.ldEntities[eid]; ok == true { + entityMap := entity.(map[string]interface{}) + if entityMap["type"] == typ[index] { + compactEntity := tb.createOriginalPayload(entity) + entities = append(entities, compactEntity) + } + } + } + tb.ldEntities_lock.Unlock() + return entities +} + +func (tb *ThinBroker) ldEntityGetByType(typs []string, link string) ([]interface{}, error) { + var entities []interface{} + typ := typs[0] + if link != "" { + typ = tb.getTypeResolved(link, typ) + if typ == "" { + err := errors.New("Type not resolved!") + return nil, err + } + } + tb.ldEntities_lock.Lock() + for _, entity := range tb.ldEntities { + entityMap := entity.(map[string]interface{}) + if entityMap["type"] == typ { + compactEntity := tb.createOriginalPayload(entity) + entities = append(entities, compactEntity) + } + } + tb.ldEntities_lock.Unlock() + return entities, nil +} + +func (tb *ThinBroker) ldEntityGetByIdPattern(idPatterns []string, typ []string) []interface{} { + var entities []interface{} + + for eid, entity := range tb.ldEntities { + entityMap := entity.(map[string]interface{}) + for index, idPattern := range idPatterns { + if strings.Contains(idPattern, ".*") && strings.Contains(idPattern, "*.") { + idPattern = strings.Trim(idPattern, ".*") + idPattern = strings.Trim(idPattern, "*.") + if strings.Contains(eid, idPattern) { + if entityMap["type"] == typ[index] { + compactEntity := tb.createOriginalPayload(entity) + entities = append(entities, compactEntity) + break + } + } + } + if strings.Contains(idPattern, ".*") { + idPattern = strings.Trim(idPattern, ".*") + if strings.HasPrefix(eid, idPattern) { + if entityMap["type"] == typ[index] { + compactEntity := tb.createOriginalPayload(entity) + entities = append(entities, compactEntity) + break + } + } + } + if strings.Contains(idPattern, "*.") { + idPattern = strings.Trim(idPattern, "*.") + if strings.HasSuffix(eid, idPattern) { + if entityMap["type"] == typ[index] { + compactEntity := tb.createOriginalPayload(entity) + entities = append(entities, compactEntity) + break + } + } + } + } + } + return entities +} + +// Registration + +func (tb *ThinBroker) UpdateCSourceRegistration(w rest.ResponseWriter, r *rest.Request) { + var context []interface{} + context = append(context, DEFAULT_CONTEXT) + if ctype := r.Header.Get("Content-Type"); ctype == "application/json" || ctype == "application/ld+json" { + rid := r.PathParam("rid") + tb.ldContextRegistrations_lock.Lock() + if _, ok := tb.ldContextRegistrations[rid]; ok == true { + tb.ldContextRegistrations_lock.Unlock() + resolved, err := tb.ExpandPayload(r, context, true) // Context in Payload = true + if err != nil { + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + sz := Serializer{} + deSerializedRegistration, err := sz.DeSerializeRegistration(resolved) + + // IDPattern check + for _, info := range deSerializedRegistration.Information { + for _, entity := range info.Entities { + if entity.IdPattern != "" { + rest.Error(w, "Registration with Entity IdPattern is not supported!", 400) + return + } + } + } + + if err != nil { + if err.Error() == "Type can not be nil!" { + rest.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err.Error() == "Endpoint value can not be nil!" { + rest.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err.Error() == "Information value can not be nil!" { + rest.Error(w, err.Error(), http.StatusBadRequest) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + tb.UpdateRegistrationInMemory(deSerializedRegistration) + tb.ldContextRegistrations_lock.Lock() + // Update registration at Discovery + _, err := tb.sendLDRegistrationToDiscovery(tb.ldContextRegistrations[rid]) + tb.ldContextRegistrations_lock.Unlock() + // Send out the response + if err != nil { + w.WriteJson(err) + } else { + w.WriteHeader(204) + } + } + } + } else { + tb.ldContextRegistrations_lock.Unlock() + rest.Error(w, "Resource not found!", 404) + return + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +func (tb *ThinBroker) UpdateRegistrationInMemory(reg CSourceRegistrationRequest) { + tb.ldContextRegistrations_lock.Lock() + + registration := tb.ldContextRegistrations[reg.Id] + if reg.Name != "" { + registration.Name = reg.Name + } + if reg.Description != "" { + registration.Description = reg.Description + } + if reg.Endpoint != "" { + registration.Endpoint = reg.Endpoint + } + if reg.Expires != "" { + registration.Expires = reg.Expires + } + if reg.Location != "" { + registration.Location = reg.Location + } + if reg.Information != nil { + registration.Information = reg.Information + } + nilTimeInterval := TimeInterval{} + if reg.ObservationInterval != nilTimeInterval { + registration.ObservationInterval = reg.ObservationInterval + } + if reg.ManagementInterval != nilTimeInterval { + registration.ManagementInterval = reg.ManagementInterval + } + if reg.ObservationSpace != nil { + registration.ObservationSpace = reg.ObservationSpace + } + if reg.OperationSpace != nil { + registration.OperationSpace = reg.OperationSpace + } + registration.ModifiedAt = reg.ModifiedAt + tb.ldContextRegistrations[reg.Id] = registration + tb.ldContextRegistrations_lock.Unlock() +} + +func (tb *ThinBroker) deleteCSourceRegistration(rid string) error { + tb.ldEntityID2RegistrationID_lock.Lock() + tb.ldContextRegistrations_lock.Lock() + + // find rid in registrations map + if registration, ok := tb.ldContextRegistrations[rid]; ok == true { + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + for _, info := range registration.Information { + + // extract eids + for _, entity := range info.Entities { + var id string + if entity.ID != "" { + id = entity.ID + } else if entity.IdPattern != "" { + id = entity.IdPattern + } + // unregister entity at discovery + err := client.UnregisterEntity(id) + if err != nil { + tb.ldContextRegistrations_lock.Unlock() + tb.ldEntityID2RegistrationID_lock.Unlock() + ERROR.Println(err) + return err + } + + // delete eid from entityID2regID map + delete(tb.ldEntityID2RegistrationID, id) + } + } + + // delete registration from registrations map + delete(tb.ldContextRegistrations, rid) + + } else { + tb.ldContextRegistrations_lock.Unlock() + tb.ldEntityID2RegistrationID_lock.Unlock() + err := errors.New("Registration not found!") + return err + } + tb.ldContextRegistrations_lock.Unlock() + tb.ldEntityID2RegistrationID_lock.Unlock() + return nil +} + +func (tb *ThinBroker) getCSourceRegByType(typs []string, link string) ([]CSourceRegistrationRequest, error) { + registrations := []CSourceRegistrationRequest{} + typ := typs[0] + if link != "" { + typ = tb.getTypeResolved(link, typ) + if typ == "" { + err := errors.New("Type not resolved!") + return nil, err + } + } + + tb.ldContextRegistrations_lock.Lock() + + for _, registration := range tb.ldContextRegistrations { + + infos := []RegistrationInfo{} + for _, info := range registration.Information { + regInfo := RegistrationInfo{} + entityExists := false + for _, entity := range info.Entities { + if entity.Type == typ { + entityExists = true + regInfo.Entities = append(regInfo.Entities, entity) + } + } + if entityExists == true { + regInfo.Properties = info.Properties + regInfo.Relationships = info.Relationships + infos = append(infos, regInfo) + } + } + registration.Information = infos + if len(registration.Information) > 0 { + registrations = append(registrations, registration) + } + } + tb.ldContextRegistrations_lock.Unlock() + return registrations, nil +} + +func (tb *ThinBroker) getCSourceRegByIdAndType(eids []string, typs []string) []CSourceRegistrationRequest { + registrations := []CSourceRegistrationRequest{} + tb.ldContextRegistrations_lock.Lock() + for index, _ := range eids { + for _, registration := range tb.ldContextRegistrations { + infos := []RegistrationInfo{} + for _, info := range registration.Information { + regInfo := RegistrationInfo{} + entityExists := false + for _, entity := range info.Entities { + if entity.Type == typs[index] && entity.ID == eids[index] { + entityExists = true + regInfo.Entities = append(regInfo.Entities, entity) + } + } + if entityExists == true { + regInfo.Properties = info.Properties + regInfo.Relationships = info.Relationships + infos = append(infos, regInfo) + } + } + registration.Information = infos + if len(registration.Information) > 0 { + registrations = append(registrations, registration) + } + } + } + tb.ldContextRegistrations_lock.Unlock() + return registrations +} + +func (tb *ThinBroker) getCSourceRegByIdPatternAndType(idPatterns []string, typs []string) []CSourceRegistrationRequest { + registrations := []CSourceRegistrationRequest{} + + for index, idPattern := range idPatterns { + if strings.Contains(idPattern, ".*") && strings.Contains(idPattern, "*.") { + idPattern = strings.Trim(idPattern, ".*") + idPattern = strings.Trim(idPattern, "*.") + regs := tb.getRebuiltLDRegistration(idPattern, typs[index]) + registrations = append(registrations, regs...) + //copy(registrations, regs) + } else if strings.Contains(idPattern, ".*") { + idPattern = strings.Trim(idPattern, ".*") + regs := tb.getRebuiltLDRegistration(idPattern, typs[index]) + registrations = append(registrations, regs...) + //registrations = copy(registrations, regs) + } else if strings.Contains(idPattern, "*.") { + idPattern = strings.Trim(idPattern, "*.") + regs := tb.getRebuiltLDRegistration(idPattern, typs[index]) + registrations = append(registrations, regs...) + //registrations = copy(registrations, regs) + } + } + return registrations +} + +func (tb *ThinBroker) getRebuiltLDRegistration(idPattern string, typ string) []CSourceRegistrationRequest { + registrations := []CSourceRegistrationRequest{} + tb.ldEntityID2RegistrationID_lock.Lock() + tb.ldContextRegistrations_lock.Lock() + + for eid, rid := range tb.ldEntityID2RegistrationID { + if strings.Contains(eid, idPattern) { + if registration, ok := tb.ldContextRegistrations[rid]; ok == true { + infos := []RegistrationInfo{} + for _, info := range registration.Information { + regInfo := RegistrationInfo{} + entityExists := false + for _, entity := range info.Entities { + if entity.Type == typ && entity.ID == eid { + entityExists = true + regInfo.Entities = append(regInfo.Entities, entity) + break + } + } + if entityExists == true { + regInfo.Properties = info.Properties + regInfo.Relationships = info.Relationships + infos = append(infos, regInfo) + } + } + registration.Information = infos + + if len(registration.Information) > 0 { + registrations = append(registrations, registration) + } + } + } + } + tb.ldContextRegistrations_lock.Unlock() + tb.ldEntityID2RegistrationID_lock.Unlock() + return registrations +} + +// Subscription +func (tb *ThinBroker) UpdateLDSubscription(w rest.ResponseWriter, r *rest.Request) { + var context []interface{} + sid := r.PathParam("sid") + if ctype := r.Header.Get("Content-Type"); ctype == "application/json" || ctype == "application/ld+json" { + if link := r.Header.Get("Link"); link != "" { + linkMap := tb.extractLinkHeaderFields(link) // Keys in returned map are: "link", "rel" and "type" + if linkMap["rel"] != DEFAULT_CONTEXT { + context = append(context, linkMap["rel"]) // Make use of "link" and "type" also + } + } + context = append(context, DEFAULT_CONTEXT) + tb.ldSubscriptions_lock.Lock() + if _, ok := tb.ldSubscriptions[sid]; ok == true { + tb.ldSubscriptions_lock.Unlock() + resolved, err := tb.ExpandPayload(r, context, false) // Context in Link header + + if err != nil { + if err.Error() == "EmptyPayload!" { + rest.Error(w, "Empty payloads are not allowed in this operation!", 400) + return + } + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + sz := Serializer{} + deSerializedSubscription, err := sz.DeSerializeSubscription(resolved) + + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + /*tb.UpdateSubscriptionInMemory(deSerializedSubscription, sid) + + // Update in discovery here. + tb.ldSubscriptions_lock.RLock() + subReq := tb.ldSubscriptions[sid] + tb.ldSubscriptions_lock.RUnlock() + + if err := tb.SubscribeLDContextAvailability(subReq); err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(204)*/ + tb.ldSubscriptions_lock.RLock() + //subReq := tb.ldSubscriptions[sid] + tb.ldSubscriptions_lock.RUnlock() + err := tb.UpdateLDContextAvailability(deSerializedSubscription, sid) + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + // update in broker memory + tb.UpdateSubscriptionInMemory(deSerializedSubscription, sid) + } + w.WriteHeader(204) + } + } + } else { + tb.ldSubscriptions_lock.Unlock() + rest.Error(w, "Resource not found!", 404) + return + } + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", 400) + return + } +} + +// update subscription context availability in discovery +func (tb *ThinBroker) UpdateLDContextAvailability(subReq LDSubscriptionRequest, sid string) error { + ctxAvailabilityRequest := SubscribeContextAvailabilityRequest{} + + for key, entity := range subReq.Entities { + if entity.IdPattern != "" { + entity.IsPattern = true + } + subReq.Entities[key] = entity + } + ctxAvailabilityRequest.Entities = subReq.Entities + ctxAvailabilityRequest.Attributes = subReq.WatchedAttributes + //copy(ctxAvailabilityRequest.Attributes, subReq.Notification.Attributes) + ctxAvailabilityRequest.Reference = tb.MyURL + "/notifyContextAvailability" + ctxAvailabilityRequest.Duration = subReq.Expires + eid := "" + for key, value := range tb.availabilitySub2MainSub { + value = tb.availabilitySub2MainSub[key] + if value == sid { + eid = key + break + } + } + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + AvailabilitySubID, err := client.UpdateLDContextAvailability(&ctxAvailabilityRequest, eid) + + if AvailabilitySubID != "" { + tb.subLinks_lock.Lock() + notifyMessage, alreadyBack := tb.tmpNGSILDNotifyCache[AvailabilitySubID] + tb.subLinks_lock.Unlock() + if alreadyBack == true { + INFO.Println("========forward the availability notify that arrived earlier===========") + tb.handleNGSI9Notify(subReq.Id, notifyMessage) + + tb.subLinks_lock.Lock() + delete(tb.tmpNGSILDNotifyCache, AvailabilitySubID) + tb.subLinks_lock.Unlock() + } + return nil + } else { + INFO.Println("failed to subscribe the availability of requested entities ", err) + return err + } +} +func (tb *ThinBroker) UpdateSubscriptionInMemory(sub LDSubscriptionRequest, sid string) { + tb.ldSubscriptions_lock.Lock() + subscription := tb.ldSubscriptions[sid] + if sub.Name != "" { + subscription.Name = sub.Name + } + if sub.Description != "" { + subscription.Description = sub.Description + } + if sub.Expires != "" { + subscription.Expires = sub.Expires + } + if sub.Status != "" { + subscription.Status = sub.Status + } + if sub.IsActive != false { + subscription.IsActive = sub.IsActive + } + if sub.Entities != nil { + subscription.Entities = sub.Entities + } + if sub.WatchedAttributes != nil { + subscription.WatchedAttributes = sub.WatchedAttributes + } + if sub.Notification.Attributes != nil { + subscription.Notification.Attributes = sub.Notification.Attributes + } + if sub.Notification.Format != "" { + subscription.Notification.Format = sub.Notification.Format + } + if sub.Notification.Endpoint.URI != "" { + subscription.Notification.Endpoint.URI = sub.Notification.Endpoint.URI + } + if sub.Notification.Endpoint.Accept != "" { + subscription.Notification.Endpoint.Accept = sub.Notification.Endpoint.Accept + } + if sub.TimeInterval != 0 { + subscription.TimeInterval = sub.TimeInterval + } + if sub.Throttling != 0 { + subscription.Throttling = sub.Throttling + + } + if sub.Q != "" { + subscription.Q = sub.Q + } + nilGeoQ := GeoQuery{} + if sub.GeoQ != nilGeoQ { + subscription.GeoQ = sub.GeoQ + } + nilTemporalQ := TemporalQuery{} + if sub.TemporalQ != nilTemporalQ { + subscription.TemporalQ = sub.TemporalQ + } + if sub.Csf != "" { + subscription.Csf = sub.Csf + } + tb.ldSubscriptions_lock.Unlock() +} + +func (tb *ThinBroker) deleteLDSubscription(sid string) error { + tb.subLinks_lock.Lock() + if aids, ok := tb.main2Other[sid]; ok == true { + // Unsubscribe at discovery + client := NGSI9Client{IoTDiscoveryURL: tb.IoTDiscoveryURL, SecurityCfg: tb.SecurityCfg} + for _, aid := range aids { + err := client.UnsubscribeContextAvailability(aid) + if err != nil { + return err + } + } + + // Unsubscribe at broker + tb.ldSubscriptions_lock.Lock() + delete(tb.main2Other, sid) + for _, aid := range aids { + delete(tb.availabilitySub2MainSub, aid) + } + delete(tb.ldSubscriptions, sid) + tb.ldSubscriptions_lock.Unlock() + tb.subLinks_lock.Unlock() + + return nil + } else { + tb.subLinks_lock.Unlock() + err := errors.New("NotFound") + return err + } +} + +func (tb *ThinBroker) getLDSubscription(sid string) *LDSubscriptionRequest { + tb.ldSubscriptions_lock.Lock() + subscription := tb.ldSubscriptions[sid] + tb.ldSubscriptions_lock.Unlock() + return subscription +} + +func (tb *ThinBroker) GetLDSubscriptions(w rest.ResponseWriter, r *rest.Request) { + if accept := r.Header.Get("Accept"); accept == "application/ld+json" { + tb.ldSubscriptions_lock.RLock() + defer tb.ldSubscriptions_lock.RUnlock() + + subscriptions := make(map[string]LDSubscriptionRequest) + + for sid, sub := range tb.ldSubscriptions { + subscriptions[sid] = *sub + } + w.WriteHeader(200) + w.WriteJson(&subscriptions) + } else { + rest.Error(w, "Missing Headers or Incorrect Header values!", http.StatusBadRequest) + } +} + +func (tb *ThinBroker) matchPattern(pattern string, id string) bool { + if strings.Contains(pattern, ".*") && strings.Contains(pattern, "*.") { + id = strings.Trim(pattern, ".*") + id = strings.Trim(pattern, "*.") + if strings.Contains(id, pattern) { + return true + } + + } else if strings.Contains(pattern, ".*") { + id = strings.Trim(pattern, ".*") + if strings.HasPrefix(id, pattern) { + return true + } + + } else if strings.Contains(pattern, "*.") { + id = strings.Trim(pattern, "*.") + if strings.HasSuffix(id, pattern) { + return true + } + } + return false +} diff --git a/broker/utils.go b/broker/utils.go index 63d56dae..0d7e9749 100644 --- a/broker/utils.go +++ b/broker/utils.go @@ -15,7 +15,7 @@ func postNotifyContext(ctxElems []ContextElement, subscriptionId string, URL str elementRespList := make([]ContextElementResponse, 0) if IsOrionBroker == true { - return postOrionV2NotifyContext(ctxElems, URL) + return postOrionV2NotifyContext(ctxElems, URL, subscriptionId) } for _, elem := range ctxElems { @@ -76,7 +76,7 @@ type OrionV2Metadata struct { } // send an notify to orion broker based on v2, for the compatability reason -func postOrionV2NotifyContext(ctxElems []ContextElement, URL string) error { +func postOrionV2NotifyContext(ctxElems []ContextElement, URL string, subscriptionId string) error { elementList := make([]map[string]interface{}, 0) for _, elem := range ctxElems { // convert it to NGSI v2 @@ -106,7 +106,7 @@ func postOrionV2NotifyContext(ctxElems []ContextElement, URL string) error { } notifyCtxReq := &OrionV2NotifyContextRequest{ - SubscriptionId: "", + SubscriptionId: subscriptionId, Entities: elementList, } @@ -377,3 +377,86 @@ func updateDomainMetadata(metadata *ContextMetadata, ctxElement *ContextElement) // add it as new metadata (*ctxElement).Metadata = append((*ctxElement).Metadata, *metadata) } + +// NGSI-LD starts here. + +func ldPostNotifyContext(ldCtxElems []map[string]interface{}, subscriptionId string, URL string /*IsOrionBroker bool,*/, httpsCfg *HTTPS) error { + INFO.Println("NOTIFY: ", URL) + elementRespList := make([]LDContextElementResponse, 0) + + //if IsOrionBroker == true { + // return postOrionV2NotifyContext(ctxElems, URL) + //} + + for _, elem := range ldCtxElems { + elementResponse := LDContextElementResponse{} + elementResponse.LDContextElement = elem + elementResponse.StatusCode.Code = 200 + elementResponse.StatusCode.ReasonPhrase = "OK" + + elementRespList = append(elementRespList, elementResponse) + } + + notifyCtxReq := &LDNotifyContextRequest{ + SubscriptionId: subscriptionId, + LDContextResponses: elementRespList, + } + + body, err := json.Marshal(notifyCtxReq) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", URL, bytes.NewBuffer(body)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + client := &http.Client{} + if strings.HasPrefix(URL, "https") == true { + client = httpsCfg.GetHTTPClient() + } + + resp, err := client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + return err + } + + ioutil.ReadAll(resp.Body) + + return nil +} + +func ldCloneWithSelectedAttributes(ldElem map[string]interface{}, selectedAttributes []string) map[string]interface{} { + if len(selectedAttributes) == 0 { + return ldElem + } else { + preparedCopy := make(map[string]interface{}) + for key, val := range ldElem { + if key == "id" { + preparedCopy["id"] = val + } else if key == "type" { + preparedCopy["type"] = val + } else if key == "createdAt" { + preparedCopy["createdAt"] = val + } else if key == "modifiedAt" { + //preparedCopy["modifiedAt"] = val + } else if key == "location" { + preparedCopy["location"] = val + } else if key == "@context" { + preparedCopy["@context"] = val + } else { + // add attribute only if present in selectedAttributes + for _, requiredAttrName := range selectedAttributes { + if key == requiredAttrName { + preparedCopy[key] = val + break + } + } + } + } + return preparedCopy + } +} diff --git a/cli/python/fogflowclient.py b/cli/python/fogflowclient.py new file mode 100644 index 00000000..dee35edc --- /dev/null +++ b/cli/python/fogflowclient.py @@ -0,0 +1,303 @@ +import requests +import json +import socketio +from threading import Lock, Thread + +class WebSocketClient(Thread): + def __init__(self, url): + Thread.__init__(self) + self.url = url + self.lock = Lock() + self.connected = False + self.subscriptions = [] + self.cbList = {} + + self.sio = socketio.Client() + #self.sio = socketio.AsyncClient() + + def setCallback(self, subscriptionId, callback): + self.lock.acquire() + self.cbList[subscriptionId] = callback + self.subscriptions.append(subscriptionId) + if self.connected == True: + self.sio.emit('subscriptions', self.subscriptions) + + self.subscriptions = [] + self.lock.release() + + def onConnect(self): + self.lock.acquire() + self.connected = True + print('connection established') + if len(self.subscriptions) > 0: + sio.emit('subscriptions', self.subscriptions) + self.subscriptions = [] + self.lock.release() + + def onNotify(self, msg): + sid = msg['subscriptionID'] + entities = msg['entities'] + + self.lock.acquire() + cb = self.cbList[sid] + self.lock.release() + + cb(entities) + + def onDisconnect(self): + self.lock.acquire() + self.connected = False + print('disconnected from server') + self.lock.release() + + def run(self): + self.sio.on('connect', self.onConnect) + self.sio.on('disconnect', self.onDisconnect) + + self.sio.on('notify', self.onNotify) + + self.sio.connect(self.url) + self.sio.wait() + + def stop(self): + self.sio.disconnect() + +class ContextEntity: + def __init__(self): + self.id = "" + self.type = "" + self.attributes = {} + self.metadata = {} + + def toContextElement(self): + ctxElement = {} + + ctxElement['entityId'] = {} + ctxElement['entityId']['id'] = self.id + ctxElement['entityId']['type'] = self.type + ctxElement['entityId']['isPattern'] = False + + ctxElement['attributes'] = [] + for key in self.attributes: + attr = self.attributes[key] + ctxElement['attributes'].append({'name': key, 'type': attr['type'], 'value': attr['value']}) + + ctxElement['domainMetadata'] = [] + for key in self.metadata: + meta = self.metadata[key] + ctxElement['domainMetadata'].append({'name': key, 'type': meta['type'], 'value': meta['value']}) + + return ctxElement + + def fromContextElement(self, ctxElement): + entityId = ctxElement['entityId']; + + self.id = entityId['id'] + self.type = entityId['type'] + + if 'attributes' in ctxElement: + for attr in ctxElement['attributes']: + self.attributes[attr['name']] = {'type': attr['type'], 'value': attr['value']} + + if 'domainMetadata' in ctxElement: + for meta in ctxElement['domainMetadata']: + self.attributes[meta['name']] = {'type': meta['type'], 'value': meta['value']} + + + def toJSON(self): + ctxElement = {} + + ctxElement['entityId'] = {} + ctxElement['entityId']['id'] = self.id + ctxElement['entityId']['type'] = self.type + ctxElement['entityId']['isPattern'] = False + + ctxElement['attributes'] = self.attributes + ctxElement['metadata'] = self.metadata + + return json.dumps(ctxElement) + +class FogFlowClient: + def __init__(self, url): + self.fogflowURL = url + self.wsclient = WebSocketClient(url) + self.wsclient.start() + + def __del__(self): + self.wsclient.stop() + + # synchronized remote call + def remoteCall(self, serviceTopology): + response = requests.get(self.fogflowURL + '/remoteCall?serviceTopology=' + serviceTopology) + print(response.text) + + # asynchronize way to trigger a service topology + def start(self, serviceTopology, callback): + # issue an intent to trigger the service topology + response = self.sendIntent('Test') + + print(response) + + # set up the callback function to handle the subscribed results + refURL = 'http://host.docker.internal:1030' + sid = self.subscribe(response['outputType'], refURL) + if sid != '': + self.wsclient.setCallback(sid, callback) + else: + print('failed to create the subscription') + + return response['id'] + + # stop the triggered service topology + def stop(self, sid): + self.removeIntent(sid) + + def sendIntent(self, serviceTopology): + intent = {} + intent['topology'] = serviceTopology + intent['priority'] = { + 'exclusive': False, + 'level': 50 + }; + intent['geoscope'] = { + 'scopeType': "global", + 'scopeValue': "global" + }; + + headers = {'Accept' : 'application/json', 'Content-Type' : 'application/json'} + response = requests.post(self.fogflowURL + '/intent', data=json.dumps(intent), headers=headers) + if response.status_code != 200: + print('failed to update context') + print(response.text) + + return response.json() + + def removeIntent(self, intentEntityId): + paramter = {} + paramter['id'] = intentEntityId + + headers = {'Accept' : 'application/json', 'Content-Type' : 'application/json'} + response = requests.delete(self.fogflowURL + '/intent', data=json.dumps(paramter), headers=headers) + if response.status_code != 200: + print('failed to remove intent') + print(response.text) + return False + else: + print('remove intent') + return True + + def put(self, ctxEntity): + headers = {'Accept' : 'application/json', 'Content-Type' : 'application/json'} + + updateCtxReq = {}; + updateCtxReq['contextElements'] = []; + updateCtxReq['contextElements'].append(ctxEntity.toContextElement()) + updateCtxReq['updateAction'] = 'UPDATE' + + response = requests.post(self.fogflowURL + '/ngsi10/updateContext', data=json.dumps(updateCtxReq), headers=headers) + if response.status_code != 200: + print('failed to update context') + print(response.text) + return False + else: + return True + + def getById(self, entityId): + queryReq = {}; + queryReq['entities'] = []; + + idObj = {} + idObj['id'] = entityId + idObj['isPattern'] = False + queryReq['entities'].append(idObj) + + headers = {'Accept' : 'application/json', 'Content-Type' : 'application/json'} + response = requests.post(self.fogflowURL + '/ngsi10/queryContext', data=json.dumps(queryReq), headers=headers) + + entityList = []; + + if response.status_code != 200: + print('failed to query context') + print(response.text) + else: + jsonData = response.json() + for element in jsonData['contextResponses']: + entity = ContextEntity() + entity.fromContextElement(element['contextElement']) + entityList.append(entity) + + return entityList + + + def getByType(self, entityType): + queryReq = {}; + queryReq['entities'] = []; + + idObj = {} + idObj['type'] = entityType + idObj['isPattern'] = True + queryReq['entities'].append(idObj) + + headers = {'Accept' : 'application/json', 'Content-Type' : 'application/json'} + response = requests.post(self.fogflowURL + '/ngsi10/queryContext', data=json.dumps(queryReq), headers=headers) + + entityList = []; + + if response.status_code != 200: + print('failed to query context') + print(response.text) + else: + jsonData = response.json() + for element in jsonData['contextResponses']: + entity = ContextEntity() + entity.fromContextElement(element['contextElement']) + entityList.append(entity) + + return entityList + + def delete(self, entityId): + contextElement = {}; + + idObj = {} + idObj['id'] = entityId + idObj['isPattern'] = False + contextElement['entityId'] = idObj + + updateCtxReq = {}; + updateCtxReq['contextElements'] = []; + updateCtxReq['contextElements'].append(contextElement) + updateCtxReq['updateAction'] = 'DELETE' + + headers = {'Accept' : 'application/json', 'Content-Type' : 'application/json'} + response = requests.post(self.fogflowURL + '/ngsi10/updateContext', data=json.dumps(updateCtxReq), headers=headers) + if response.status_code != 200: + print('failed to delete context entity ' + entityId) + print(response.text) + return False + else: + print('delete context entity ' + entityId) + return True + + + def subscribe(self, entityType, refURL): + subscribeCtxReq = {}; + subscribeCtxReq['entities'] = []; + + idObj = {} + idObj['type'] = entityType + idObj['isPattern'] = True + subscribeCtxReq['entities'].append(idObj) + + subscribeCtxReq['reference'] = refURL; + + headers = {'Accept' : 'application/json', 'Content-Type' : 'application/json'} + response = requests.post(self.fogflowURL + '/ngsi10/subscribeContext', data=json.dumps(subscribeCtxReq), headers=headers) + + if response.status_code != 200: + print('failed to subscribe') + print(response.text) + return '' + else: + return response.json()['subscribeResponse']['subscriptionId'] + + \ No newline at end of file diff --git a/cli/python/test.py b/cli/python/test.py new file mode 100644 index 00000000..c706b02d --- /dev/null +++ b/cli/python/test.py @@ -0,0 +1,65 @@ +import json +import time +import os + +from fogflowclient import FogFlowClient, ContextEntity + + +def onResult(ctxEntity): + print(ctxEntity) + + +def main(): + ffclient = FogFlowClient("http://localhost") + + #push entities to the FogFlow system + for i in range(5): + #create the input data + deviceID = "Device.Car." + str(i) + + tempSensor = ContextEntity() + tempSensor.id = deviceID + tempSensor.type = "Car" + tempSensor.attributes["temperature"] = {'type': 'integer', 'value': 30} + tempSensor.metadata["location"] = { + "type":"point", + "value":{ + "latitude":35.97800618085566, + "longitude":139.41650390625003 + } + } + + ffclient.put(tempSensor) + + time.sleep(2) + + #trigger a service topology and then subscribe to the generated result + sessionId = ffclient.start("test", onResult) + + while True: + try: + time.sleep(3) + except: + print("exit") + + entities = ffclient.getById(tempSensor.id) + for entity in entities: + print(entity.toJSON()) + + entities = ffclient.getByType(tempSensor.type) + for entity in entities: + print(entity.toJSON()) + + for i in range(5): + deviceID = "Device.Car." + str(i) + ffclient.delete(deviceID) + + #stop the service topology + ffclient.stop(sessionId) + + os._exit(1) + + + +if __name__ == "__main__": + main() diff --git a/common/constants/constants.go b/common/constants/constants.go new file mode 100644 index 00000000..2edacabb --- /dev/null +++ b/common/constants/constants.go @@ -0,0 +1,54 @@ +package constants + +const DEFAULT_CONTEXT string = "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" + +const ID string = "@id" +const TYPE string = "@type" +const VALUE string = "@value" +const DATE_TIME string = "https://uri.etsi.org/ngsi-ld/DateTime" +const PROPERTY string = "https://uri.etsi.org/ngsi-ld/Property" +const RELATIONSHIP string = "https://uri.etsi.org/ngsi-ld/Relationship" +const PROVIDED_BY string = "https://uri.etsi.org/ngsi-ld/default-context/providedBy" +const HAS_VALUE string = "https://uri.etsi.org/ngsi-ld/hasValue" +const HAS_OBJECT string = "https://uri.etsi.org/ngsi-ld/hasObject" +const OBJECT string = "https://uri.etsi.org/ngsi-ld/default-context/Object" +const CREATED_AT string = "https://uri.etsi.org/ngsi-ld/createdAt" +const MODIFIED_AT string = "https://uri.etsi.org/ngsi-ld/modifiedAt" +const OBSERVED_AT string = "https://uri.etsi.org/ngsi-ld/observedAt" +const DATASET_ID string = "https://uri.etsi.org/ngsi-ld/datasetId" +const INSTANCE_ID string = "https://uri.etsi.org/ngsi-ld/instanceId" +const UNIT_CODE string = "https://uri.etsi.org/ngsi-ld/unitCode" + +const LOCATION string = "https://uri.etsi.org/ngsi-ld/location" +const GEO_PROPERTY string = "https://uri.etsi.org/ngsi-ld/GeoProperty" +const COORDINATES string = "https://uri.etsi.org/ngsi-ld/coordinates" +const POINT string = "https://uri.etsi.org/ngsi-ld/default-context/Point" +const LINE_STRING string = "https://uri.etsi.org/ngsi-ld/default-context/LineString" +const POLYGON string = "https://uri.etsi.org/ngsi-ld/default-context/Polygon" +const MULTI_POINT string = "https://uri.etsi.org/ngsi-ld/default-context/MultiPoint" +const MULTI_LINE_STRING string = "https://uri.etsi.org/ngsi-ld/default-context/MultiLineString" +const MULTI_POLYGON string = "https://uri.etsi.org/ngsi-ld/default-context/MultiPolygon" +const GEOMETRY_COLLECTION string = "https://uri.etsi.org/ngsi-ld/default-context/GeometryCollection" +const GEOMETRIES string = "https://uri.etsi.org/ngsi-ld/default-context/geometries" + +const W3_ID string = "http://www.w3.org/ns/json-ld#id" +const W3_TYPE string = "http://www.w3.org/ns/json-ld#type" +const ID_PATTERN string = "https://uri.etsi.org/ngsi-ld/idPattern" +const NAME string = "https://uri.etsi.org/ngsi-ld/name" +const TIMESTAMP string = "https://uri.etsi.org/ngsi-ld/default-context/timestamp" +const START string = "https://uri.etsi.org/ngsi-ld/start" +const END string = "https://uri.etsi.org/ngsi-ld/end" +const DESCRIPTION string = "https://uri.etsi.org/ngsi-ld/description" +const ENDPOINT string = "https://uri.etsi.org/ngsi-ld/endpoint" +const EXPIRES string = "https://uri.etsi.org/ngsi-ld/expires" +const INFORMATION string = "https://uri.etsi.org/ngsi-ld/information" +const PROPERTIES string = "https://uri.etsi.org/ngsi-ld/properties" +const ENTITIES string = "https://uri.etsi.org/ngsi-ld/entities" +const RELATIONSHIPS string = "https://uri.etsi.org/ngsi-ld/relationships" + +const NOTIFICATION string = "https://uri.etsi.org/ngsi-ld/notification" +const WATCHED_ATTRIBUTES string = "https://uri.etsi.org/ngsi-ld/watchedAttributes" +const FORMAT string = "https://uri.etsi.org/ngsi-ld/format" +const URI string = "https://uri.etsi.org/ngsi-ld/uri" +const ACCEPT string = "https://uri.etsi.org/ngsi-ld/accept" +const ATTRIBUTES string = "https://uri.etsi.org/ngsi-ld/attributes" diff --git a/common/ngsi/ngsi.go b/common/ngsi/ngsi.go index 1f280910..90f49c0f 100644 --- a/common/ngsi/ngsi.go +++ b/common/ngsi/ngsi.go @@ -752,6 +752,7 @@ type Subscriber struct { RequireReliability bool BrokerURL string NotifyCache []*ContextElement + LDNotifyCache []map[string]interface{} } type SubscribeContextRequest struct { @@ -1036,3 +1037,214 @@ type FiwareData struct { FiwareService string FiwareServicePath string } + +// NGSI-LD starts here. + +type LDContextElementResponse struct { + LDContextElement interface{} `json:"contextElement"` + StatusCode StatusCode `json:"statusCode"` +} + +type LDNotifyContextRequest struct { + SubscriptionId string `json:"subscriptionId",omitemtpy` + Originator string `json:"originator",omitemtpy` + LDContextResponses []LDContextElementResponse `json:"contextResponses,omitempty"` +} + +type LDContextElement struct { + Id string `json:"id",omitemtpy` + Type string `json:"type",omitemtpy` + Properties []Property `json:"properties",omitempty` + Relationships []Relationship `json:"relationships",omitempty` + CreatedAt string `json:"createdAt",omitemtpy` + Location LDLocation `json:"location",omitempty` + ObservationSpace GeoProperty `json:"observationSpace",omitempty` + OperationSpace GeoProperty `json:"operationSpace",omitempty` + ModifiedAt string `json:"modifiedAt"` +} + +type GeoProperty struct { + Type string `json:"type",omitemtpy` + Value interface{} `json:"value"omitemtpy` + ObservedAt string `json:"observedAt", omitemtpy` + DatasetId string `json:"datasetId", omitempty` //URI + // + // +} + +/* +func (ldce *LDContextElement) CloneWithSelectedAttributes(selectedAttributes []string) *LDContextElement { + preparedCopy := LDContextElement{} + preparedCopy.Id = ldce.Id + preparedCopy.Type = ldce.Type + preparedCopy.CreatedAt = ldce.CreatedAt + preparedCopy.Location = ldce.Location + + if len(selectedAttributes) == 0 { + preparedCopy.Properties = make([]Property, len(ldce.Properties)) + copy(preparedCopy.Properties, ldce.Properties) + + preparedCopy.Relationships = make([]Relationship, len(ldce.Relationships)) + copy(preparedCopy.Relationships, ldce.Relationships) + } else { + preparedCopy.Properties = make([]Property, 0) + preparedCopy.Relationships = make([]Relationship, 0) + + for _, requiredAttrName := range selectedAttributes { + attrFound := false + for _, property := range ldce.Properties { + if property.Name == requiredAttrName { + attrFound = true + preparedCopy.Properties = append(preparedCopy.Properties, property) + break + } + } + if attrFound != true { + for _, relationship := range ldce.Relationships { + if relationship.Name == requiredAttrName { + preparedCopy.Relationships = append(preparedCopy.Relationships, relationship) + break + } + } + } + } + } + + return &preparedCopy +} */ + +type LDLocation struct { + Type string `json:"type",omitemtpy` + Value interface{} `json:"value",omitemtpy` +} + +type LDLocationValue struct { + Type string `json:"type",omitemtpy` + Coordinates interface{} `json:"coordinates",omitemtpy` + Geometries []Geometry `json:"geometries",omitemtpy` +} + +type Geometry struct { + Type string `json:"type",omitemtpy` + Coordinates interface{} `json:"coordinates",omitemtpy` +} + +type Property struct { + Name string `json:"name",omitemtpy` + Type string `json:"type",omitemtpy` + Value interface{} `json:"value",omitemtpy` // Can also be a string or a JSON object + ObservedAt string `json:"observedAt",omitempty` + DatasetId string `json:"DatasetId",omitempty` //<>, Optional. + InstanceId string `json:"InstanceId",omitempty` //<> uniquely identifying a relationship instance. System Generated, Optional. + CreatedAt string `json:"createdAt",omitemtpy` + ModifiedAt string `json:"modifiedAt",omitemtpy` + UnitCode string `json:"UnitCode",omitempty` + ProvidedBy ProvidedBy `json:"providedBy",omitempty` + Properties []Property + Relationships []Relationship +} + +type Relationship struct { + Name string `json:"name",omitemtpy` + Type string `json:"type",omitemtpy` + Object string `json:object,omitemtpy` //<>, Mandatory + ObservedAt string `json:"observedAt",omitempty` + ProvidedBy ProvidedBy `json:"providedBy",omitempty` + DatasetId string `json:"DatasetId",omitempty` //<>, Optional. + InstanceId string `json:"InstanceId",omitempty` //<> uniquely identifying a relationship instance. System Generated, Optional. + CreatedAt string `json:"createdAt",omitemtpy` + ModifiedAt string `json:"modifiedAt",omitemtpy` + Properties []Property + Relationships []Relationship +} + +type ProvidedBy struct { + Type string `json:"type",omitemtpy` + Object string `json:"object",omitemtpy` +} + +type LDSubscriptionRequest struct { + Id string `json:"id",omitempty` //URI, if missing, will be assigned during subscription phase and returned to client + Type string `json:"type",omitemtpy` //should be equal to "Subscription" + Name string `json:"name",omitempty` + Description string `json:"description",omitempty` + Entities []EntityId `json:"entities",omitempty` + WatchedAttributes []string `json:"watchedAttributes",omitempty` + TimeInterval uint `json:"timeInterval",omitempty` + Q string `json:"q",omitempty` + GeoQ GeoQuery `json:"geoQ",omitempty` + Csf string `json:"csf",omitempty` + IsActive bool `json:"isActive",omitempty` + Notification NotificationParams `json:"notification"` + Expires string `json:"expires",omitempty` + Throttling uint `json:"throttling",omitempty` + TemporalQ TemporalQuery `json:"temporalQ",omitempty` + Status string `json:"status",omitempty` + Subscriber Subscriber `json:"subscriber,omitempty` + CreatedAt string `json:"createdAt",omitemtpy` + ModifiedAt string `json:"modifiedAt",omitemtpy` +} + +type GeoQuery struct { + Geometry string `json:"geometry",omitemtpy` + Coordinates string `json:"coordinates",omitemtpy` // string or JSON Array + GeoRel string `json:"georel",omitemtpy` + GeoProperty string `json:"geoproperty",omitempty` +} + +type NotificationParams struct { + Attributes []string `json:"attributes",omitempty` + Format string `json:"format",omitempty` + Endpoint Endpoint `json:"endpoint",omitemtpy` + Status string `json:"status",omitempty` + TimeSent uint `json:"timeSent",omitempty` + LastNotification string `json:"lastNotification",omitempty` + LastFailure string `json:"lastFailure",omitempty` + LastSuccess string `json:"lastSuccess",omitempty` +} + +type Endpoint struct { + URI string `json:"uri",omitemtpy` // URI + Accept string `json:"accept",omitempty` +} + +type TemporalQuery struct { + TimeRel string `json:"timerel",omitemtpy` + Time string `json:"time",omitemtpy` + EndTime string `json:"endTime",omitempty` + TimeProperty string `json:"timeproperty",omitempty` +} + +type CSourceRegistrationRequest struct { + Id string `json:"id",omitempty` //URI + Type string `json:"type",omitemtpy` + Name string `json:"name",omitempty` + Description string `json:"description",omitempty` + Information []RegistrationInfo `json:"information",omitemtpy` + ObservationInterval TimeInterval `json:"observationInterval",omitempty` + ManagementInterval TimeInterval `json:"managementInterval",omitempty` + Location string `json:"location",omitempty` + ObservationSpace interface{} `json:"observationSpace,omitempty"` // Type = GeoJSON Geometry + OperationSpace interface{} `json:"operationSpace,omitempty"` // Type = GeoJSON Geometry + Expires string `json:expires,omitempty` + Endpoint string `json:"endpoint",omitemtpy` //URI + CreatedAt string `json:"createdAt",omitemtpy` + ModifiedAt string `json:"modifiedAt",omitemtpy` + // +} + +type RegistrationInfo struct { + Entities []EntityId `json:"entities",omitempty` + Properties []string `json:"properties,omitempty"` + Relationships []string `json:"relationships,omitempty"` +} + +type TimeInterval struct { + Start string `json:"start",omitemtpy` //DateTime value + End string `json:"end",omitempty` //DateTime value +} + +type CSourceRegistrationResponse struct { + RegistrationID string `json: "registrationID",omitemtpy` + ErrorCode StatusCode `json:"errorCode,omitempty",omitemtpy` +} diff --git a/common/ngsi/ngsiclient.go b/common/ngsi/ngsiclient.go index 6e259f53..225a2294 100644 --- a/common/ngsi/ngsiclient.go +++ b/common/ngsi/ngsiclient.go @@ -785,3 +785,193 @@ func (nc *NGSI9Client) SendHeartBeat(brokerProfile *BrokerProfile) error { return err } + +// NGSI-LD feature addition + +func (nc *NGSI10Client) CreateLDEntityOnRemote(elem map[string]interface{}, link string) error { + body, err := json.Marshal(elem) + if err != nil { + return err + } + req, err := http.NewRequest("POST", nc.IoTBrokerURL+"/ngsi-ld/v1/entities/", bytes.NewBuffer(body)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/ld+json") + + if link != "" { + req.Header.Add("Link", link) + } + + client := nc.SecurityCfg.GetHTTPClient() + resp, err := client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + ERROR.Println(err) + return err + } + return nil +} + +func (nc *NGSI10Client) AppendLDEntityOnRemote(elem map[string]interface{}, eid string) error { + body, err := json.Marshal(elem) + if err != nil { + return err + } + req, err := http.NewRequest("POST", nc.IoTBrokerURL+"/ngsi-ld/v1/entities/"+eid+"/attrs", bytes.NewBuffer(body)) + req.Header.Add("Content-Type", "application/json") + + client := nc.SecurityCfg.GetHTTPClient() + resp, err := client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + ERROR.Println(err) + return err + } + return nil +} + +func (nc *NGSI10Client) UpdateLDEntityAttributeOnRemote(elem map[string]interface{}, eid string) (error, int) { + body, err := json.Marshal(elem) + if err != nil { + return err, 0 + } + req, err := http.NewRequest("PATCH", nc.IoTBrokerURL+"/ngsi-ld/v1/entities/"+eid+"/attrs", bytes.NewBuffer(body)) + req.Header.Add("Content-Type", "application/json") + + client := nc.SecurityCfg.GetHTTPClient() + resp, err := client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + ERROR.Println(err) + return err, 0 + } + if resp.StatusCode == 207 { + return err, resp.StatusCode + } + return nil, 0 +} + +func (nc *NGSI10Client) UpdateLDEntityspecificAttributeOnRemote(elem map[string]interface{}, eid string, attribute string) (error, int) { + body, err := json.Marshal(elem) + if err != nil { + return err, 0 + } + req, err := http.NewRequest("PATCH", nc.IoTBrokerURL+"/ngsi-ld/v1/entities/"+eid+"/attrs/"+attribute, bytes.NewBuffer(body)) + req.Header.Add("Content-Type", "application/json") + + client := nc.SecurityCfg.GetHTTPClient() + resp, err := client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + ERROR.Println(err) + return err, 0 + } + if resp.StatusCode == 404 { + return err, resp.StatusCode + } + return nil, 0 +} + +func (nc *NGSI10Client) SubscribeLdContext(sub *LDSubscriptionRequest, requireReliability bool) (string, error) { + body, err := json.Marshal(*sub) + if err != nil { + return "", err + } + req, err := http.NewRequest("POST", nc.IoTBrokerURL+"/ngsi-ld/v1/subscriptions/", bytes.NewBuffer(body)) + req.Header.Add("Content-Type", "application/ld+json") + req.Header.Add("Accept", "application/ld+json") + req.Header.Add("Link", "<{{link}}>; rel=\"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"; type=\"application/ld+json\"") + if requireReliability == true { + req.Header.Add("Require-Reliability", "true") + } + + client := nc.SecurityCfg.GetHTTPClient() + resp, err := client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + ERROR.Println(err) + return "", err + } + + text, _ := ioutil.ReadAll(resp.Body) + + subscribeCtxResp := SubscribeContextResponse{} + err = json.Unmarshal(text, &subscribeCtxResp) + if err != nil { + return "", err + } + + if subscribeCtxResp.SubscribeResponse.SubscriptionId != "" { + return subscribeCtxResp.SubscribeResponse.SubscriptionId, nil + } else { + err = errors.New(subscribeCtxResp.SubscribeError.ErrorCode.ReasonPhrase) + return "", err + } +} + +//Query for NGSILD entity with entityId + +func (nc *NGSI10Client) QueryForNGSILDEntity(eid string) int { + req, _ := http.NewRequest("GET", nc.IoTBrokerURL+"/ngsi-ld/v1/entities/"+eid, nil) + req.Header.Add("Content-Type", "application/ld+json") + req.Header.Add("Accept", "application/ld+json") + client := nc.SecurityCfg.GetHTTPClient() + resp, _ := client.Do(req) + return resp.StatusCode + +} + +// Query for NGSIV1 entity with entityId + +func (nc *NGSI10Client) QueryForNGSIV1Entity(eid string) int { + req, _ := http.NewRequest("GET", nc.IoTBrokerURL+"/entity/"+eid, nil) + client := nc.SecurityCfg.GetHTTPClient() + resp, _ := client.Do(req) + return resp.StatusCode + +} + +// client to update subscribe Context availbility on discovery +func (nc *NGSI9Client) UpdateLDContextAvailability(sub *SubscribeContextAvailabilityRequest, sid string) (string, error) { + body, err := json.Marshal(*sub) + if err != nil { + return "", err + } + req, err := http.NewRequest("POST", nc.IoTDiscoveryURL+"/UpdateLDContextAvailability"+"/"+sid, bytes.NewBuffer(body)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + client := nc.SecurityCfg.GetHTTPClient() + resp, err := client.Do(req) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + ERROR.Println(err) + return "", err + } + + text, _ := ioutil.ReadAll(resp.Body) + + subscribeCtxAvailResp := SubscribeContextAvailabilityResponse{} + err = json.Unmarshal(text, &subscribeCtxAvailResp) + if err != nil { + return "", err + } + + if subscribeCtxAvailResp.SubscriptionId != "" { + return subscribeCtxAvailResp.SubscriptionId, nil + } else { + err = errors.New(subscribeCtxAvailResp.ErrorCode.ReasonPhrase) + return "", err + } +} diff --git a/designer/build b/designer/build index 6bb2b2f6..3c72f863 100755 --- a/designer/build +++ b/designer/build @@ -1,4 +1,4 @@ -npm install +#npm install docker build -t "fogflow/designer" . diff --git a/designer/config.json b/designer/config.json index 84ab22ff..3c8387a4 100644 --- a/designer/config.json +++ b/designer/config.json @@ -1,7 +1,7 @@ { - "coreservice_ip": "host.docker.internal", - "external_hostip": "host.docker.internal", - "internal_hostip": "host.docker.internal", + "coreservice_ip": "127.0.0.1", + "external_hostip": "127.0.0.1", + "internal_hostip": "127.0.0.1", "physical_location":{ "longitude": 139, "latitude": 35 @@ -38,5 +38,8 @@ }, "https": { "enabled" : false - } + }, + "persistent_storage": { + "port": 9082 + } } diff --git a/designer/dgraph.js b/designer/dgraph.js new file mode 100644 index 00000000..529391fd --- /dev/null +++ b/designer/dgraph.js @@ -0,0 +1,258 @@ +const dgraph = require("dgraph-js"); +const grpc = require("grpc"); +var request = require('request'); +var config_fs_name = './config.json'; +var axios = require('axios') +var fs = require('fs'); +const bodyParser = require('body-parser'); +var globalConfigFile = require(config_fs_name) +var config = globalConfigFile.designer; +config.grpcPort = globalConfigFile.persistent_storage.port; +config.HostIp = globalConfigFile.external_hostip; +config.brokerIp=globalConfigFile.coreservice_ip +config.brokerPort=globalConfigFile.broker.http_port + +/* + creating grpc client for making connection with dgraph +*/ + +async function newClientStub() { + return new dgraph.DgraphClientStub(config.HostIp+":"+config.grpcPort, grpc.credentials.createInsecure()); +} + +// Create a client. + +async function newClient(clientStub) { + return new dgraph.DgraphClient(clientStub); +} + +// Drop All - discard all data and start from a clean slate. +/*async function dropAll(dgraphClient) { + const op = new dgraph.Operation(); + op.setDropAll(true); + await dgraphClient.alter(op); +}*/ + +/* + create scheema for node +*/ + +async function setSchema(dgraphClient) { + const schema = ` + attributes: [uid] . + domainMetadata: [uid] . + entityId: uid . + updateAction: string . + id: string . + isPattern: bool . + latitude: float . + longitude: float . + name: string . + type: string . + value: string . + `; + const op = new dgraph.Operation(); + op.setSchema(schema); + await dgraphClient.alter(op); +} + +/* + convert object domainmetadata data into string to store entity as single node +*/ + +async function resolveDomainMetaData(data) { + if ('domainMetadata' in data) { + var len=data.domainMetadata.length + for(var i=0;i < len; i++) { + if('value' in data.domainMetadata[i]) { + if(data.domainMetadata[i].type != 'global' && data.domainMetadata[i].type != 'stringQuery'){ + data.domainMetadata[i].value=JSON.stringify(data.domainMetadata[i].value) + } + } + } + } +} + +/* + convert object attributes data into string to store entity as single node +*/ + +async function resolveAttributes(data) { + if ('attributes' in data){ + var length=data.attributes.length + for(var i=0;i < length; i++) { + if('type' in data.attributes[i]) { + if (data.attributes[i].type=='object') + data.attributes[i].value=JSON.stringify(data.attributes[i].value) + else { + data.attributes[i].value=data.attributes[i].value.toString() + } + } + } + } +} + +/* + insert data into database +*/ + +async function createData(dgraphClient,ctx) { + const txn = dgraphClient.newTxn(); + try { + const mu = new dgraph.Mutation(); + mu.setSetJson(ctx); + const response = await txn.mutate(mu); + await txn.commit(); + } + finally { + await txn.discard(); + } +} + +/* + send data to cloud broker +*/ + +async function sendData(contextEle) { + var updateCtxReq = {}; + updateCtxReq.contextElements = []; + updateCtxReq.updateAction = 'UPDATE' + updateCtxReq.contextElements.push(contextEle) + await axios({ + method: 'post', + url: 'http://'+config.brokerIp+':'+config.brokerPort+'/ngsi10/updateContext', + data: updateCtxReq + }).then( function(response){ + if (response.status == 200) { + return response.data; + } else { + return null; + } + }); +} + +/* + convert string object into structure to register data into cloud broker +*/ + +async function changeFromDataToobject(contextElement) { + contextEle=contextElement['contextElements'] + for (var ctxEle=0; ctxEle < contextEle.length; ctxEle=ctxEle+1) { + ctxEleReq=contextEle[ctxEle] + if('attributes' in ctxEleReq) { + for(var ctxAttr=0; ctxAttr { + //bug-113 fix + console.log('Retrying.. There is an Unhandled Rejection at:', promise, 'reason:', reason); + queryForEntity(); +}); + + +async function queryForEntity() { + const dgraphClientStub = await newClientStub(); + const dgraphClient = await newClient(dgraphClientStub); + await setSchema(dgraphClient); + await queryData(dgraphClient); + await dgraphClientStub.close(); +} + +module.exports={db,queryForEntity} diff --git a/designer/dockerfile b/designer/dockerfile index 7ec1587d..e576d727 100644 --- a/designer/dockerfile +++ b/designer/dockerfile @@ -1,5 +1,14 @@ -FROM node:alpine +FROM node:11-alpine + WORKDIR /app -ADD . /app + +ADD config.json /app +ADD package.json /app + +RUN npm install + +ADD main.js /app +ADD dgraph.js /app +ADD ./public /app/public ENTRYPOINT [ "node", "main.js" ] diff --git a/designer/dockerfile4arm b/designer/dockerfile4arm index 857889dc..2dbde22e 100644 --- a/designer/dockerfile4arm +++ b/designer/dockerfile4arm @@ -1,5 +1,5 @@ FROM arm32v6/node:8.14.0-alpine WORKDIR /app ADD . /app - +RUN npm install ENTRYPOINT [ "node", "main.js" ] diff --git a/designer/main.js b/designer/main.js index 4b6fa295..79fb8d2c 100644 --- a/designer/main.js +++ b/designer/main.js @@ -2,7 +2,10 @@ var request = require('request'); var express = require('express'); var multer = require('multer'); var https = require('https'); - +const bodyParser = require('body-parser'); +var axios = require('axios') +var dgraph=require('./dgraph.js'); +var jsonParser = bodyParser.json(); var config_fs_name = './config.json'; @@ -20,6 +23,11 @@ var NGSIClient = require('./public/lib/ngsi/ngsiclient.js'); var config = globalConfigFile.designer; +// get the URL of the cloud broker +var cloudBrokerURL = "http://" + globalConfigFile.external_hostip + ":" + globalConfigFile.broker.http_port + "/ngsi10" +config.agentIP = globalConfigFile.external_hostip; +config.agentPort = globalConfigFile.designer.agentPort; + // set the agent IP address from the environment variable config.agentIP = globalConfigFile.external_hostip; config.agentPort = globalConfigFile.designer.agentPort; @@ -29,8 +37,30 @@ config.brokerURL = './ngsi10'; config.webSrvPort = globalConfigFile.designer.webSrvPort +config.brokerIp=globalConfigFile.coreservice_ip +//broker port +config.brokerPort=globalConfigFile.broker.http_port +//designer IP +config.designerIP=globalConfigFile.coreservice_ip +config.DGraphPort=globalConfigFile.persistent_storage.port + + console.log(config); +dgraph.queryForEntity() + +function uuid() { + var uuid = "", i, random; + for (i = 0; i < 32; i++) { + random = Math.random() * 16 | 0; + if (i == 8 || i == 12 || i == 16 || i == 20) { + uuid += "-" + } + uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16); + } + + return uuid; +} // all subscriptions that expect data forwarding var subscriptions = {}; @@ -64,6 +94,83 @@ app.post('/photo',function(req, res){ }); }); + +// create a new intent to trigger the corresponding service topology +app.post('/intent', jsonParser, function(req, res){ + intent = req.body + + var intentCtxObj = {}; + + var uid = uuid(); + + intentCtxObj.entityId = { + id: 'ServiceIntent.' + uid, + type: 'ServiceIntent', + isPattern: false + }; + + intentCtxObj.attributes = {}; + intentCtxObj.attributes.status = {type: 'string', value: 'enabled'}; + intentCtxObj.attributes.intent = {type: 'object', value: intent}; + + intentCtxObj.metadata = {}; + intentCtxObj.metadata.location = intent.geoscope; + + ngsi10client = new NGSIClient.NGSI10Client(cloudBrokerURL); + ngsi10client.updateContext(intentCtxObj).then( function(data) { + console.log('======create intent======'); + console.log(data); + }).catch(function(error) { + console.log(error); + console.log('failed to create intent'); + }); + + // prepare the response + reply = {'id': intentCtxObj.entityId.id, 'outputType': 'Result'} + res.json(reply); +}); + +/* + api to create fogFlow internal entities +*/ + +app.use (bodyParser.json()); +app.post('/ngsi10/updateContext', async function (req, res) { + var payload=await req.body + var response = await axios({ + method: 'post', + url: 'http://'+config.brokerIp+':'+config.brokerPort+'/ngsi10/updateContext', + data: payload + }) + if (response.status == 200) { + await dgraph.db(payload) + } + res.send(response.data) +}); + + +// to remove an existing intent +app.delete('/intent', jsonParser, function(req, res){ + eid = req.body.id + + var entityid = { + id : eid, + isPattern: false + }; + + ngsi10client = new NGSIClient.NGSI10Client(cloudBrokerURL); + ngsi10client.deleteContext(entityid).then( function(data) { + console.log('======delete intent======'); + console.log(data); + }).catch(function(error) { + console.log(error); + console.log('failed to delete intent'); + }); + + res.end("OK"); +}); + + app.get('/config.js', function(req, res){ res.setHeader('Content-Type', 'application/json'); var data = 'var config = ' + JSON.stringify(config) + '; ' @@ -89,7 +196,7 @@ function handleNotify(req, ctxObjects, res) { for(var i = 0; i < ctxObjects.length; i++) { console.log(ctxObjects[i]); var client = subscriptions[sid]; - client.emit('notify', ctxObjects[i]); + client.emit('notify', {'subscriptionID': sid, 'entities': ctxObjects[i]}); } } } @@ -125,3 +232,5 @@ io.on('connection', function (client) { } }); }); + + diff --git a/designer/package.json b/designer/package.json index 3f917695..b9352e24 100644 --- a/designer/package.json +++ b/designer/package.json @@ -8,12 +8,14 @@ "start": "node main.js" }, "dependencies": { - "request": "*", - "express": "*", - "axios": "*", - "multer": "*", - "logops": "*", - "socket.io": "*", - "body-parser": "*" + "axios": "^0.19.2", + "body-parser": "^1.19.0", + "dgraph-js": "^20.3.0", + "express": "^4.17.1", + "grpc": "*", + "logops": "^2.1.1", + "multer": "^1.4.2", + "request": "^2.88.2", + "socket.io": "^2.3.0" } } diff --git a/designer/public/js/function.js b/designer/public/js/function.js index a78ea817..12e40963 100644 --- a/designer/public/js/function.js +++ b/designer/public/js/function.js @@ -23,6 +23,9 @@ var blocks = null; // client to interact with IoT Broker var client = new NGSI10Client(config.brokerURL); +//DGraph +// to interact with designer +var clientDes = new NGSIDesClient(config.designerIP+':'+config.webSrvPort); var myFogFunctionExamples = [ { @@ -301,7 +304,7 @@ function boardScene2Topology(scene) console.log("=============submit a fog function============="); console.log(JSON.stringify(functionCtxObj)); - return client.updateContext(functionCtxObj).then( function(data1) { + return clientDes.updateContext(functionCtxObj).then( function(data1) { console.log(data1); showFogFunctions(); }).catch( function(error) { @@ -320,7 +323,7 @@ function submitFogFunction(functionCtxObj) geoScope.value = "global" functionCtxObj.metadata.location = geoScope; - return client.updateContext(functionCtxObj).then( function(data1) { + return clientDes.updateContext(functionCtxObj).then( function(data1) { console.log(data1); }).catch( function(error) { console.log('failed to record the created fog function'); @@ -533,7 +536,7 @@ function deleteFogFunction(fogfunction) console.log("delete a fog function"); console.log(functionEntity); - client.deleteContext(functionEntity).then( function(data) { + clientDes.deleteContext(functionEntity).then( function(data) { console.log(data); showFogFunctions(); }).catch( function(error) { diff --git a/designer/public/js/operator.js b/designer/public/js/operator.js index fa09b978..56ee5e5e 100644 --- a/designer/public/js/operator.js +++ b/designer/public/js/operator.js @@ -6,6 +6,10 @@ var handlers = {}; //connect to the broker var client = new NGSI10Client(config.brokerURL); +//DGraph +// to interact with designer +var clientDes = new NGSIDesClient(config.designerIP+':'+config.webSrvPort); + console.log(config.brokerURL); addMenuItem('Operator', showOperator); @@ -185,7 +189,7 @@ function submitOperator(operator, designboard) operatorObj.metadata.location = geoScope; - client.updateContext(operatorObj).then( function(data) { + clientDes.updateContext(operatorObj).then( function(data) { showOperator(); }).catch( function(error) { console.log('failed to submit the defined operator'); @@ -557,7 +561,7 @@ function addDockerImage(image) geoScope.value = "global" newImageObject.metadata.location = geoScope; - client.updateContext(newImageObject).then( function(data) { + clientDes.updateContext(newImageObject).then( function(data) { console.log(data); }).catch( function(error) { console.log('failed to register the new device object'); @@ -674,7 +678,7 @@ function registerDockerImage() value: operatorName }; - client.updateContext(newImageObject).then( function(data) { + clientDes.updateContext(newImageObject).then( function(data) { console.log(data); // show the updated image list diff --git a/designer/public/js/topology.js b/designer/public/js/topology.js index bf19372a..a3fffad6 100644 --- a/designer/public/js/topology.js +++ b/designer/public/js/topology.js @@ -28,6 +28,10 @@ var blocks = null; // client to interact with IoT Broker var client = new NGSI10Client(config.brokerURL); +//DGraph +//to interact with Designer +var clientDes = new NGSIDesClient(config.designerIP+':'+config.webSrvPort); + var myToplogyExamples = [ { topology: {"name":"anomaly-detection","description":"detect anomaly events in shops","tasks":[{"name":"Counting","operator":"counter","input_streams":[{"selected_type":"Anomaly","selected_attributes":[],"groupby":"ALL","scoped":true}],"output_streams":[{"entity_type":"Stat"}]},{"name":"Detector","operator":"anomaly","input_streams":[{"selected_type":"PowerPanel","selected_attributes":[],"groupby":"EntityID","scoped":true},{"selected_type":"Rule","selected_attributes":[],"groupby":"ALL","scoped":false}],"output_streams":[{"entity_type":"Anomaly"}]}]}, @@ -169,7 +173,7 @@ function deleteTopology(topologyEntity) isPattern: false }; - client.deleteContext(entityid).then( function(data) { + clientDes.deleteContext(entityid).then( function(data) { console.log(data); updateTopologyList(); }).catch( function(error) { @@ -346,7 +350,7 @@ function submitTopology(topology, designboard) geoScope.value = "global" topologyCtxObj.metadata.location = geoScope; - client.updateContext(topologyCtxObj).then( function(data) { + clientDes.updateContext(topologyCtxObj).then( function(data) { console.log(data); // update the list of submitted topologies showTopologies(); @@ -700,7 +704,7 @@ function submitIntent() console.log(JSON.stringify(intentCtxObj)); - client.updateContext(intentCtxObj).then( function(data) { + clientDes.updateContext(intentCtxObj).then( function(data) { console.log(data); // update the list of submitted intents showIntents(); diff --git a/designer/public/lib/ngsi/ngsiclient.js b/designer/public/lib/ngsi/ngsiclient.js index 775c75c1..b4f9bf8a 100644 --- a/designer/public/lib/ngsi/ngsiclient.js +++ b/designer/public/lib/ngsi/ngsiclient.js @@ -179,6 +179,65 @@ var NGSI10Client = (function() { return NGSI10Client; })(); +//DGraph changes + +var NGSIDesClient = (function() { + // initialized with the designer URL + var NGSIDesClient = function(url) { + console.log('url is',url); + this.webSrvPort = url; + }; + + // update context + NGSIDesClient.prototype.updateContext = function updateContext(ctxObj) { + contextElement = JSONObject2CtxElement(ctxObj); + + var updateCtxReq = {}; + updateCtxReq.contextElements = []; + updateCtxReq.contextElements.push(contextElement) + updateCtxReq.updateAction = 'UPDATE' + + console.log(updateCtxReq); + + return axios({ + method: 'post', + url: 'http://'+this.webSrvPort+'/ngsi10/updateContext', + data: updateCtxReq + }).then( function(response){ + if (response.status == 200) { + return response.data; + } else { + return null; + } + }); + }; + + // delete context + NGSIDesClient.prototype.deleteContext = function deleteContext(entityId) { + var contextElement = {}; + contextElement.entityId = entityId + + var updateCtxReq = {}; + updateCtxReq.contextElements = []; + updateCtxReq.contextElements.push(contextElement) + updateCtxReq.updateAction = 'DELETE' + + return axios({ + method: 'post', + url: 'http://'+this.webSrvPort + '/ngsi10/updateContext', + data: updateCtxReq + }).then( function(response){ + if (response.status == 200) { + return response.data; + } else { + return null; + } + }); + }; + return NGSIDesClient; +}) +(); + var NGSI9Client = (function() { // initialized with the address of IoT Discovery var NGSI9Client = function(url) { @@ -240,11 +299,13 @@ var NGSI9Client = (function() { // initialize the exported object for this module, both for nodejs and browsers if (typeof module !== 'undefined' && typeof module.exports !== 'undefined'){ this.axios = require('axios') + module.exports.NGSIDesClient = NGSIDesClient; module.exports.NGSI10Client = NGSI10Client; module.exports.NGSI9Client = NGSI9Client; module.exports.CtxElement2JSONObject = CtxElement2JSONObject; module.exports.JSONObject2CtxElement = JSONObject2CtxElement; } else { + window.NGSIDesClient = NGSIDesClient; window.NGSI10Client = NGSI10Client; window.NGSI9Client = NGSI9Client; window.CtxElement2JSONObject = CtxElement2JSONObject; diff --git a/designer/public/lib/ngsi/ngsiproxy.js b/designer/public/lib/ngsi/ngsiproxy.js index 41b7a9dc..44a7e04a 100644 --- a/designer/public/lib/ngsi/ngsiproxy.js +++ b/designer/public/lib/ngsi/ngsiproxy.js @@ -13,9 +13,9 @@ var NGSIProxy = (function() { self.socket.emit('subscriptions', self.subscriptions); }); this.socket.on('notify', function(data) { - //console.log(data); + entities = data.entities; if(self.notifyHandler) { - self.notifyHandler(data); + self.notifyHandler(entities); } }); }; diff --git a/discovery/fastDiscovery.go b/discovery/fastDiscovery.go index adac59ed..34ac6ef9 100644 --- a/discovery/fastDiscovery.go +++ b/discovery/fastDiscovery.go @@ -208,11 +208,13 @@ func (fd *FastDiscovery) SubscribeContextAvailability(w rest.ResponseWriter, r * return } subID := u1.String() + subscribeCtxAvailabilityReq.SubscriptionId = subID // add the new subscription fd.subscriptions_lock.Lock() fd.subscriptions[subID] = &subscribeCtxAvailabilityReq + fd.subscriptions_lock.Unlock() // send out the response @@ -227,6 +229,39 @@ func (fd *FastDiscovery) SubscribeContextAvailability(w rest.ResponseWriter, r * go fd.handleSubscribeCtxAvailability(&subscribeCtxAvailabilityReq) } +//receive updateContextAvailability for subscription +func (fd *FastDiscovery) UpdateLDContextAvailability(w rest.ResponseWriter, r *rest.Request) { + sid := r.PathParam("sid") + subscribeCtxAvailabilityReq := SubscribeContextAvailabilityRequest{} + err := r.DecodeJsonPayload(&subscribeCtxAvailabilityReq) + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } + subID := sid + subscribeCtxAvailabilityReq.SubscriptionId = subID + + // add the new subscription + fd.subscriptions_lock.Lock() + if sid != "" { + fd.subscriptions[subID] = &subscribeCtxAvailabilityReq + } + fd.subscriptions_lock.Unlock() + + // send out the response + subscribeCtxAvailabilityResp := SubscribeContextAvailabilityResponse{} + subscribeCtxAvailabilityResp.SubscriptionId = subID + subscribeCtxAvailabilityResp.Duration = subscribeCtxAvailabilityReq.Duration + subscribeCtxAvailabilityResp.ErrorCode.Code = 200 + subscribeCtxAvailabilityResp.ErrorCode.ReasonPhrase = "OK" + + w.WriteJson(&subscribeCtxAvailabilityResp) + // trigger the process to send out the matched context availability infomation to the subscriber + if sid != "" { + go fd.handleSubscribeCtxAvailability(&subscribeCtxAvailabilityReq) + } +} + // handle NGSI9 subscription based on the local database func (fd *FastDiscovery) handleSubscribeCtxAvailability(subReq *SubscribeContextAvailabilityRequest) { entityMap := fd.repository.queryEntities(subReq.Entities, subReq.Attributes, subReq.Restriction) diff --git a/discovery/main.go b/discovery/main.go index 2a21c357..d90412e2 100644 --- a/discovery/main.go +++ b/discovery/main.go @@ -45,6 +45,7 @@ func main() { rest.Post("/ngsi9/subscribeContextAvailability", iotDiscovery.SubscribeContextAvailability), rest.Post("/ngsi9/unsubscribeContextAvailability", iotDiscovery.UnsubscribeContextAvailability), rest.Delete("/ngsi9/registration/#eid", iotDiscovery.deleteRegisteredEntity), + rest.Post("/ngsi9/UpdateLDContextAvailability/#sid", iotDiscovery.UpdateLDContextAvailability), // convenient ngsi9 API rest.Get("/ngsi9/registration/#eid", iotDiscovery.getRegisteredEntity), diff --git a/discovery/repository.go b/discovery/repository.go index 8057811e..4d64b22a 100644 --- a/discovery/repository.go +++ b/discovery/repository.go @@ -54,9 +54,26 @@ func (er *EntityRepository) updateRegistrationInMemory(entity EntityId, registra existRegistration.Type = entity.Type } + attrilist := make(map[string]ContextRegistrationAttribute) // update existing attribute table for _, attr := range registration.ContextRegistrationAttributes { + //existRegistration.AttributesList[:0] existRegistration.AttributesList[attr.Name] = attr + attrilist[attr.Name] = attr + } + + for _, attributeOld := range existRegistration.AttributesList { + //fmt.Println("\n---inside attribute print---\n",attributeOld.Name) + found := false + for _, attributeNew := range attrilist { + if attributeNew.Name == attributeOld.Name { + found = true + break + } + } + if found == false { + delete(existRegistration.AttributesList, attributeOld.Name) + } } // update existing metadata table @@ -97,6 +114,26 @@ func (er *EntityRepository) updateRegistrationInMemory(entity EntityId, registra return er.ctxRegistrationList[eid] } +/*func(er *EntityRepository) updateDeletedAttribute(entity EntityId, registration *ContextRegistration) { + er.ctxRegistrationList_lock.Lock() + defer er.ctxRegistrationList_lock.Unlock() + + eid := entity.ID + fmt.Println("\n--------entity id--------\n",eid) + + if existRegistration, exist := er.ctxRegistrationList[eid]; exist { + + // update existing attribute table + for _, attr := range registration.ContextRegistrationAttributes { + fmt.Println("\n---Print attr---\n",attr) + //existRegistration.AttributesList[attr.Name] = attr + //fmt.Println("\n---pront---\n",existRegistration.AttributesList[attr.Name]) + existRegistration.AttributesList[attr.Name] = attr + } + + return er.ctxRegistrationList[eid] +}*/ + func (er *EntityRepository) queryEntities(entities []EntityId, attributes []string, restriction Restriction) map[string][]EntityId { return er.queryEntitiesInMemory(entities, attributes, restriction) } diff --git a/discovery/utils.go b/discovery/utils.go index 1cff5538..a93cdf67 100644 --- a/discovery/utils.go +++ b/discovery/utils.go @@ -10,8 +10,13 @@ import ( func matchingWithFilters(registration *EntityRegistration, idFilter []EntityId, attrFilter []string, metaFilter Restriction) bool { // (1) check entityId part entity := EntityId{} + if strings.HasPrefix(registration.Type, "https://uri.etsi.org/ngsi-ld/default-context/") { + entity.Type = registration.Type + } else { + entity.Type = registration.Type + } entity.ID = registration.ID - entity.Type = registration.Type + //entity.Type = registration.Type entity.IsPattern = false atLeastOneMatched := false for _, tmp := range idFilter { diff --git a/doc/en/source/api.rst b/doc/en/source/api.rst index 0c95bb21..960843c6 100644 --- a/doc/en/source/api.rst +++ b/doc/en/source/api.rst @@ -1143,6 +1143,977 @@ Here is the Javascript-based code example to terminate a service topology by del }); +NGSI-LD Supported API's +============================ +The following figure shows a brief overview of how the APIs in current scope will be used to achieve the goal of NGSI-LD API support in FogFlow. The API support includes Entity creation, registration, subscription and notification. + +.. figure:: figures/ngsild_architecture.png + + + + +Entities API +------------ +For the purpose of interaction with Fogflow, IOT devices approaches broker with entity creation request where it is resolved as per given context. Broker further forwards the registration request to Fogflow Discovery in correspondence to the created entity. + +.. note:: Use port 80 for accessing the cloud broker, whereas for edge broker, the default port is 8070. The localhost is the coreservice IP for the system hosting fogflow. + +**POST /ngsi-ld/v1/entities** + +**a. To create NGSI-LD context entity, with context in Link in Header** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +============= =========================================================== +key Value +============= =========================================================== +Content-Type application/json +Accept application/ld+json +Link <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; + type="application/ld+json" +============= =========================================================== + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/entities/' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"' \ + -d ' + { + "id": "urn:ngsi-ld:Vehicle:A100", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + }' + + + + +**b. To create entity with context in request payload** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +============= ====================================== +key Value +============= ====================================== +Content-Type application/json +Accept application/ld+json +============= ====================================== + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/entities/' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/ld+json' \ + -d' + { + "@context": [{ + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "speed": "http://example.org/vehicle/speed", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + } + }], + "id": "urn:ngsi-ld:Vehicle:A4580", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + }' + + +**c. To create a new NGSI-LD context entity, with context in Link header and request payload is already expanded** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +============= ====================================== +key Value +============= ====================================== +Content-Type application/json +Accept application/ld+json +============= ====================================== + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/entities/' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/ld+json' \ + -d' + { + "http://example.org/vehicle/brandName": [ + { + "@type": [ + "http://uri.etsi.org/ngsi-ld/Property" + ], + "http://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": "Mercedes" + } + ] + } + ], + "http://uri.etsi.org/ngsi-ld/createdAt": [ + { + "@type": "http://uri.etsi.org/ngsi-ld/DateTime", + "@value": "2017-07-29T12:00:04" + } + ], + "@id": "urn:ngsi-ld:Vehicle:A8866", + "http://example.org/common/isParked": [ + { + "http://uri.etsi.org/ngsi-ld/hasObject": [ + { + "@id": "urn:ngsi-ld:OffStreetParking:Downtown1" + } + ], + "http://uri.etsi.org/ngsi-ld/observedAt": [ + { + "@type": "http://uri.etsi.org/ngsi-ld/DateTime", + "@value": "2017-07-29T12:00:04" + } + ], + "http://example.org/common/providedBy": [ + { + "http://uri.etsi.org/ngsi-ld/hasObject": [ + { + "@id": "urn:ngsi-ld:Person:Bob" + } + ], + "@type": [ + "http://uri.etsi.org/ngsi-ld/Relationship" + ] + } + ], + "@type": [ + "http://uri.etsi.org/ngsi-ld/Relationship" + ] + } + ], + "http://uri.etsi.org/ngsi-ld/location": [ + { + "@type": [ + "http://uri.etsi.org/ngsi-ld/GeoProperty" + ], + "http://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": "{ \"type\":\"Point\", \"coordinates\":[ -8.5, 41.2 ] }" + } + ] + } + ], + "http://example.org/vehicle/speed": [ + { + "@type": [ + "http://uri.etsi.org/ngsi-ld/Property" + ], + "http://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": 80 + } + ] + } + ], + "@type": [ + "http://example.org/vehicle/Vehicle" + ] + + }' + + +**d. To append additional attributes to an existing entity** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**POST /ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs** + +============= ====================================== +key Value +============= ====================================== +Content-Type application/json +============= ====================================== + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs' \ + -H 'Content-Type: application/json' \ + -d' + { + "@context": { + "brandName1": "http://example.org/vehicle/brandName1" + }, + "brandName1": { + "type": "Property", + "value": "BMW" + } + }' + + +**e. To update specific attributes of an existing entity** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**PATCH /ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs** + +============= ====================================== +key Value +============= ====================================== +Content-Type application/json +============= ====================================== + +**Request** + +.. code-block:: console + + curl -iX PATCH\ + 'http://localhost:80/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs' \ + -H 'Content-Type: application/json' \ + -d' + { + "@context": { + "brandName1": "http://example.org/vehicle/brandName1" + }, + "brandName1": { + "type": "Property", + "value": "AUDI" + } + }' + +**f. To update the value of a specific attribute of an existing entity** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**PATCH /ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs/brandName** + +============= ====================================== +key Value +============= ====================================== +Content-Type application/json +============= ====================================== + +**Request** + +.. code-block:: console + + curl -iX PATCH\ + 'http://localhost:80/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs/brandName' \ + -H 'Content-Type: application/json' \ + -d' + { + "@context": { + "brandName": "http://example.org/vehicle/brandName" + }, + "value": "BMW" + }' + + +**g. To delete an NGSI-LD context entity** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**DELETE /ngsi-ld/v1/entities/#eid** + +============== ============================ +Param Description +============== ============================ +eid Entity Id +============== ============================ + +**Example:** + +.. code-block:: console + + curl -iX DELETE http://localhost:80/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100 + + +**h. To delete an attribute of an NGSI-LD context entity** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**DELETE /ngsi-ld/v1/entities/#eid/attrs/#attrName** + +============== ============================ +Param Description +============== ============================ +eid Entity Id +attrName Attribute Name +============== ============================ + +**Example:** + +.. code-block:: console + + curl -iX DELETE http://localhost:80/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs/brandName1 + + +**i. To retrieve a specific entity** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/entities/#eid** + +============== ============================ +Param Description +============== ============================ +eid Entity Id +============== ============================ + +**Example:** + +.. code-block:: console + + curl http:///ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4569 + + +**j. To retrieve entities by attributes** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/entities?attrs=(Value 1)** + +============== ============================ +Param Description +============== ============================ +Value 1 Attriute Value +============== ============================ + +**Example:** + +.. code-block:: console + + curl http:///ngsi-ld/v1/entities?attrs=http://example.org/vehicle/brandName + + +**k. To retrieve a specific entity by ID and Type** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/entities?id=(value 1)&type=(value 2)** + +============== ============================ +Param Description +============== ============================ +value 1 Attribute Value of Entity +Value 2 Type Value of Entity +============== ============================ + +**Example:** + +.. code-block:: console + + curl http:///ngsi-ld/v1/entities?id=urn:ngsi-ld:Vehicle:A4569&type=http://example.org/vehicle/Vehicle + + +**l. To retrieve a specific entity by Type** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/entities?type=(Value 1)** + +============== ============================ +Param Description +============== ============================ +Value 1 Type Value +============== ============================ + +**Example:** + +.. code-block:: console + + curl http://localhost:80//ngsi-ld/v1/entities?type=http://example.org/vehicle/Vehicle + + +**m. To retrieve a specific entity by Type, context in Link Header** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET ngsi-ld/v1/entities?type=(Value 1)** + +============== ============================ +Param Description +============== ============================ +Value 1 Type Value +============== ============================ + +**Header Format** + +============= =========================================================== +key Value +============= =========================================================== +Content-Type application/json +Accept application/ld+json +Link <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; + type="application/ld+json" +============= =========================================================== + +**Example:** + +.. code-block:: console + + curl -H "Link : https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" http://localhost:80//ngsi-ld/v1/entities?type=Vehicle + + +**n. To retrieve a specific entity by IdPattern and Type** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET : /ngsi-ld/v1/entities?idPattern=(Value 1)&type=(Value 2)** + +============== ============================ +Param Description +============== ============================ +value 1 idPattern Value of Entity +Value 2 Type Value of Entity +============== ============================ + +**Example:** + +.. code-block:: console + + curl http://localhost:80/ngsi-ld/v1/entities?idPattern=urn:ngsi-ld:Vehicle:A.*&type=http://example.org/vehicle/Vehicle + + +Csource Registration API +--------------------------- + +When the registration request approaches broker, it stores the registration detail with itself and forwards a request towards discovery. Discovery looks for the subscriber and initiate the process of notifying them, about the availability of data at specific broker. + +**POST /ngsi-ld/v1/csourceRegistrations** + +**a. To create a new context source registration, with context in link header** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +============= =========================================================== +key Value +============= =========================================================== +Content-Type application/json +Accept application/ld+json +Link <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; + type="application/ld+json" +============= =========================================================== + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/csourceRegistrations' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"' \ + -d ' + { + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3459", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A456", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "idPattern": ".*downtown$", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://my.csource.org:1026", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15" + }' + + +**b. To create a new context source registration, with context in request payload** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +============= ====================================== +key Value +============= ====================================== +Content-Type application/json +============= ====================================== + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/csourceRegistrations' \ + -H 'Content-Type: application/json' \ + -d' + { + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3458", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A456", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "idPattern": ".*downtown$", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://my.csource.org:1026", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", + "@context": [ + + "https://forge.etsi.org/gitlab/NGSI-LD/NGSI-LD/raw/master/coreContext/ngsi-ld-core-context.jsonld", + { + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "brandName1": "http://example.org/vehicle/brandName1", + "speed": "http://example.org/vehicle/speed", + "totalSpotNumber": "http://example.org/parking/totalSpotNumber", + "reliability": "http://example.org/common/reliability", + "OffStreetParking": "http://example.org/parking/OffStreetParking", + "availableSpotNumber": "http://example.org/parking/availableSpotNumber", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "http://example.org/common/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + }, + "name": "http://example.org/common/name" + } + ] + }' + + +**c. To update an existing context source registration, with context in request payload** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +**PATCH /ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a3458** + +============= ====================================== +key Value +============= ====================================== +Content-Type application/json +============= ====================================== + +**Request** + +.. code-block:: console + + curl -iX PATCH\ + 'http://localhost:80/ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a3458' \ + -H 'Content-Type: application/json' \ + -d' + { + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3458", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A456", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed", + "brandName1" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "idPattern": ".*downtown$", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://my.csource.org:1026", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", + "@context": [ + + "https://forge.etsi.org/gitlab/NGSI-LD/NGSI-LD/raw/master/coreContext/ngsi-ld-core-context.jsonld", + { + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "brandName1": "http://example.org/vehicle/brandName1", + "speed": "http://example.org/vehicle/speed", + "totalSpotNumber": "http://example.org/parking/totalSpotNumber", + "reliability": "http://example.org/common/reliability", + "OffStreetParking": "http://example.org/parking/OffStreetParking", + "availableSpotNumber": "http://example.org/parking/availableSpotNumber", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "http://example.org/common/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + }, + "name": "http://example.org/common/name", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "expires":"http://uri.etsi.org/ngsi-ld/expires" + } + ] + }' + + +**d. To delete an existing context source registration based on registration id** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**DELETE /ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:#contRegid** + +============== ============================ +Param Description +============== ============================ +contRegid Context Registration Id +============== ============================ + +**Example:** + +.. code-block:: console + + curl -iX DELETE http:///ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a3458 + + +**e. To get a registration by Type** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/csourceRegistrations?type=(Value 1)** + +============== ============================ +Param Description +============== ============================ +Value 1 Type Value +============== ============================ + +**Example:** + +.. code-block:: console + + curl http:///ngsi-ld/v1/csourceRegistrations?type=http://example.org/vehicle/Vehicle + + +**f. To get a registration by Type, context in Link Header** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/csourceRegistrations?type=(Value 1)** + +============== ============================ +Param Description +============== ============================ +Value 1 Type Value +============== ============================ + +**Header Format** + +============= =========================================================== +key Value +============= =========================================================== +Content-Type application/json +Accept application/ld+json +Link <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; + type="application/ld+json" +============= =========================================================== + +**Example:** + +.. code-block:: console + + curl -H "Link : https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" http:///ngsi-ld/v1/csourceRegistrations?type=Vehicle + + +**g. To get a registration by ID and Type** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/csourceRegistrations?id=(Value 1)&type=(Value 2)** + +============== ============================ +Param Description +============== ============================ +Value 1 Registration ID Value of Entity +Value 2 Type Value of Entity +============== ============================ + +**Example:** + +.. code-block:: console + + curl http:///ngsi-ld/v1/csourceRegistrations?id=urn:ngsi-ld:Vehicle:C1234&type=http://example.org/vehicle/Vehicle + + +**h. To get a registration by IdPattern and Type** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/csourceRegistrations?idPattern=(Value 1)&type=(Value 2)** + +============== ============================ +Param Description +============== ============================ +Value 1 idPattern Value of Entity +Value 2 Type Value of Entity +============== ============================ + +**Example:** + +.. code-block:: console + + curl http://ngsi-ld/v1/csourceRegistrations?idPattern=urn:ngsi-ld:Vehicle:C.*&type=http://example.org/vehicle/Vehicle + + +Subscription API +------------------- + +A new subscription is issued by the subscriber which is enrouted to broker where the details of subscriber is stored for notification purpose. The broker initiate a request to Fogflow Discovery, where this is registered as new subscription and looks for availabltiy of corresponding data. On receiving data is passes the information back to subscribing broker. + + +**a. To create a new Subscription to with context in Link header** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**POST /ngsi-ld/v1/subscriptions** + +**Header Format** + +============= =========================================================== +key Value +============= =========================================================== +Content-Type application/ld+json +Link <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; + type="application/ld+json" +============= =========================================================== + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/subscriptions' \ + -H 'Content-Type: application/ld+json' \ + -H 'Link: "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"' \ + -d ' + { + "type": "Subscription", + "entities": [{ + "idPattern": ".*", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName"], + "notification": { + "attributes": ["brandName"], + "format": "keyValues", + "endpoint": { + "uri": "http://my.endpoint.org/notify", + "accept": "application/json" + } + } + }' + + +**b. To retrieve all the subscriptions** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/subscriptions** + +**Example:** + +.. code-block:: console + + curl http://localhost.80/ngsi-ld/subscriptions + + +**c. To retrieve a specific subscription based on subscription id** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**GET /ngsi-ld/v1/subscriptions/#sid** + +============== ============================ +Param Description +============== ============================ +sid subscription Id +============== ============================ + +**Example:** + +.. code-block:: console + + curl http://localhost:80/ngsi-ld/subscriptions/urn:ngsi-ld:Subscription:71 + + +**d. To update a specific subscription based on subscription id, with context in Link header** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**PATCH /ngsi-ld/v1/subscriptions/#sid** + +============== ============================ +Param Description +============== ============================ +sid subscription Id +============== ============================ + + +**Header Format** + +============= =========================================================== +key Value +============= =========================================================== +Content-Type application/ld+json +Link <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; + type="application/ld+json" +============= =========================================================== + + +**Request** + +.. code-block:: console + + curl -iX POST\ + 'http://localhost:80/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:71' \ + -H 'Content-Type: application/ld+json' \ + -H 'Link: "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"' \ + -d ' + { + "id": "urn:ngsi-ld:Subscription:7", + "type": "Subscription", + "entities": [{ + "type": "Vehicle" + }], + "watchedAttributes": ["http://example.org/vehicle/brandName2"], + "q":"http://example.org/vehicle/brandName2!=Mercedes", + "notification": { + "attributes": ["http://example.org/vehicle/brandName2"], + "format": "keyValues", + "endpoint": { + "uri": "http://my.endpoint.org/notify", + "accept": "application/json" + } + } + }' + + +**e. To delete a specific subscription based on subscription id** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +**DELETE /ngsi-ld/v1/subscriptions/#sid** + +============== ============================ +Param Description +============== ============================ +sid subscription Id +============== ============================ + + +**Example:** + +.. code-block:: console + + curl -iX DELETE http://localhost:80/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:71 + diff --git a/doc/en/source/core_concept.rst b/doc/en/source/core_concept.rst index 8ae8cbb9..038883e5 100644 --- a/doc/en/source/core_concept.rst +++ b/doc/en/source/core_concept.rst @@ -96,3 +96,43 @@ The main procedure is illustrated by the following figure, including two major s according to certain optimization objectives. Currently, the task assignment in FogFlow is optimized to reduce across-node data traffic without overloading any edge node. + +FogFlow Storage +====================== + +Previously, FogFlow was using its internal data structure to store the FogFlow internal entities +like operator, Fog-function, docker images and service-topology. FogFlow was not supporting any permanent +storage to store FogFlow internal NGSI entities. Hence, it loses all stored internal entities whenever FogFlow broker went down. +So, to resolve this problem FogFlow is using a Persistent Storage named DGraph. + +Persistent storage is a data storage device that retains data after power to that device is shut off. +It is also sometimes referred to as non-volatile storage. + +The Dgraph data model consists of data sets, records, and attributes. Where Records are the fundamental +units of data in the Dgraph and an attribute is the basic unit of a record schema. Assignments from attributes +(also known as key-value pairs) describe records in the Dgraph. The flow diagram of data with persistent storage is as below: + + + +.. figure:: figures/persistent_data_flow.png + + + +1. User of FogFlow can create the FogFlow internal entities using Web browser through designer. + +2. User of FogFlow can create the FogFlow internal entities using client(curl) through designer. + +3. Designer can store and get the created entities from the Dgraph database in case of requirement. + +4. Designer can get the old registered entities from the Dgraph database and can registered in the cloud broker. + + +There are many databases available that support Graph Database for example: Neo4j, DGraph are among the top using databases. +FogFlow is using DGraph, reason behind selecting DGraph is as below: + +1. Dgraph is 160x faster than Neo4j for loading graph data. + +2. Dgraph consumes 5x lesser memory compared to Neo4j. + +3. Dgraph supports most of the functionality that one needs to get the job done. + diff --git a/doc/en/source/figures/FogFlow_System_Design.png b/doc/en/source/figures/FogFlow_System_Design.png index 7ce1a1b2..533da856 100644 Binary files a/doc/en/source/figures/FogFlow_System_Design.png and b/doc/en/source/figures/FogFlow_System_Design.png differ diff --git a/doc/en/source/figures/architectureDiagram.png b/doc/en/source/figures/architectureDiagram.png new file mode 100644 index 00000000..f015a1db Binary files /dev/null and b/doc/en/source/figures/architectureDiagram.png differ diff --git a/doc/en/source/figures/detailDescriptionofSampleRequest.png b/doc/en/source/figures/detailDescriptionofSampleRequest.png new file mode 100644 index 00000000..9bd97479 Binary files /dev/null and b/doc/en/source/figures/detailDescriptionofSampleRequest.png differ diff --git a/doc/en/source/figures/detailDescriptionofSampleRequest2.png b/doc/en/source/figures/detailDescriptionofSampleRequest2.png new file mode 100644 index 00000000..a75b3d12 Binary files /dev/null and b/doc/en/source/figures/detailDescriptionofSampleRequest2.png differ diff --git a/doc/en/source/figures/gitGuideline.jpg b/doc/en/source/figures/gitGuideline.jpg new file mode 100644 index 00000000..46a8a716 Binary files /dev/null and b/doc/en/source/figures/gitGuideline.jpg differ diff --git a/doc/en/source/figures/keyrock_Account_Portal.png b/doc/en/source/figures/keyrock_Account_Portal.png new file mode 100644 index 00000000..a89e4b01 Binary files /dev/null and b/doc/en/source/figures/keyrock_Account_Portal.png differ diff --git a/doc/en/source/figures/ngsild_architecture.png b/doc/en/source/figures/ngsild_architecture.png new file mode 100644 index 00000000..a804855d Binary files /dev/null and b/doc/en/source/figures/ngsild_architecture.png differ diff --git a/doc/en/source/figures/persistent_data_flow.png b/doc/en/source/figures/persistent_data_flow.png new file mode 100644 index 00000000..988243e7 Binary files /dev/null and b/doc/en/source/figures/persistent_data_flow.png differ diff --git a/doc/en/source/figures/responseFromKeyrock.png b/doc/en/source/figures/responseFromKeyrock.png new file mode 100644 index 00000000..b2f06966 Binary files /dev/null and b/doc/en/source/figures/responseFromKeyrock.png differ diff --git a/doc/en/source/figures/security_architecture.png b/doc/en/source/figures/security_architecture.png new file mode 100644 index 00000000..b428d1c7 Binary files /dev/null and b/doc/en/source/figures/security_architecture.png differ diff --git a/doc/en/source/guideline.rst b/doc/en/source/guideline.rst index 861420e2..6c1ca66a 100644 --- a/doc/en/source/guideline.rst +++ b/doc/en/source/guideline.rst @@ -25,39 +25,53 @@ Note that contribution workflows themselves (e.g. pull requests, etc.) are descr - Pull Request protocol -=========================== +Branch Management Guidelines +------------------------------- +.. figure:: figures/gitGuideline.jpg -As explained in `FIWARE Development Guidelines`_, contributions are done using a pull request (PR). The detailed "protocol" used in such PR a is described below: +Community can have two main branches with an infinite lifetime: -.. _`FIWARE Development Guidelines`: https://forge.fiware.org/plugins/mediawiki/wiki/fiware/index.php/Developer_Guidelines +1. **Master branch**:This is a highly stable branch that is always production-ready and contains the last release version of source code in production. +2. **Development branch**: Derived from the master branch, the development branch serves as a branch for integrating different features planned for an upcoming release. This branch may or may not be as stable as the master branch. It is where developers collaborate and merge feature branches.All of the changes should be merged back into master somehow and then tagged with a release number. + + +Apart from those two primary branches, there are other branches in the workflow: + +- **Feature Branch**:Forked from the development branch for feature development i.e.enhancement or documnetation. Merged back to the development branch after feature development or enhacement implementation. + +- **Bug Branch**:Ramify from the development branch.Merged back to the development branch after bug fixing. + +- **Hotfix branch**:Hotfix branches are created from the master branch. It is the current production release running live and causing troubles due to a severe bug.But changes on development are yet unstable. We may then branch off a hotfix branch and start fixing the problem. It should be rarest occasion, in case only critical bugs. + +**Note**:Only NLE and NECTI members have privilege to create and merge Hotfix branch. + +.. list-table:: **Branch naming convention** + :widths: 20 40 40 + :header-rows: 1 + + * - Branch + - Branches naming guideline + - Remarks + + * - Feature branches + - Must branch from: *development*. Must merge back into: *development* .Branch naming convention: *feature-feature_id* + - *feature_id* is the Github issue id from **https://github.com/smartfog/fogflow/issues** + + * - Bug Branches + - Must branch from: *development*. Must merge back into: *development* .Branch naming convention: *bug-bug_id* + - *bug_id* is the Github issue id from **https://github.com/smartfog/fogflow/issues** + + * - Hotfix Branches + - Must branch from: *master branch*.Must merge back into: *master branch*.Branch naming convention: *hotfix-bug number*. + - *Bug number* is the Github issue id from **https://github.com/smartfog/fogflow/issues** + +Permissions to the branches: +******************************* + +- **Master** - We tend to very strict that only NLE members and privileged members of NECTI can merge on Master branch and accept the pull requests. Pull requests to master can be raised by only NECTI OR NLE members. -* Direct commits to master branch (even single-line modifications) are not allowed. Every modification has to come as a PR. -* In case the PR is implementing/fixing a numbered issue, the issue number has to be referenced in the body of the PR at creation time. -* Anybody is welcome to provide comments to the PR (either direct comments or using the review feature offered by Github). -* Use *code line comments* instead of *general comments*, for traceability reasons (see comments lifecycle below). - -* Comments lifecycle - - * Comment is created, initiating a *comment thread*. - * New comments can be added as responses to the original one, starting a discussion. - * After discussion, the comment thread ends in one of the following ways: - - - 'Fixed in < commit hash >' in case the discussion involves a fix in the PR branch (which commit hash is - included as reference). - - 'NTC' , if finally nothing needs to be done (NTC = Nothing To Change). - - * PR can be merged when the following conditions are met: - * All comment threads are closed. - * All the participants in the discussion have provided a 'LGTM' general comment (LGTM = Looks good to me) - * Self-merging is not allowed (except in rare and justified circumstances). - -Some additional remarks to take into account when contributing with new PRs: - -* PR must include not only code contributions, but their corresponding pieces of documentation (new or modifications to existing one) and tests. -* PR modifications must pass full regression based on existing test (unit, functional, memory, e2e) in addition to whichever new test added due to the new functionality. -* PR should be of an appropriated size that makes review achievable. Too large PRs could be closed with a "please, redo the work in smaller pieces" without any further discussing. +- **Development** - Any community member can raise the pull request to the development branch but it should be reviewed by NLE or NECTI members.Development branches commits will be moved to master branch only when all the test cases define in travis.yml, will run successfully. Code style guidelines diff --git a/doc/en/source/https.rst b/doc/en/source/https.rst index 333ec986..39155f2e 100644 --- a/doc/en/source/https.rst +++ b/doc/en/source/https.rst @@ -187,3 +187,363 @@ FogFlow dashboard can be opened in web browser to see the current system status If self-signed SSL certificate is being used, a browser warning indication can be seen that the crtificate should not be trusted. It can be proceeded past this warning to view the FogFlow dashboard web page via https. + + + +Secure FogFlow using Identity Management +========================================== + +Identity management(IdM) is a process for identifying, authenticating individuals or groups to have access to applications or system by associating some auth token with established identities. IdM is the task of controlling data about users or applications. In this tutorialFogFlow Designer security implementation and secure Cloud-Edge communication is explained and tested. + + +Terminology +--------------- + +**Keyrock**: `Keyrock`_ is the FIWARE component responsible for Identity Management. Keyrock also provide feature to add OAuth2-based authentication and authorization security in order to secure services and applications. + +**PEP Proxy Wilma**: `PEP Proxy Wilma`_ is a FIWARE Generic Enabler that enhances the performance of Identity Management. It combines with Keyrock to secure access to endpoints exposed by FIWARE Generic Enablers. Wilma listens for any request, authenticates it from Keyrock and stores it in its cache for a limited period of time. If a new request arrives, Wilma will first check in its cache and if any grant is stored, it will directly authenticate otherwise it will send the request to Keyrock for authentication. + +.. _`Keyrock`: https://fiware-idm.readthedocs.io/en/latest/ +.. _`PEP Proxy Wilma`: https://fiware-pep-proxy.readthedocs.io/en/latest/ + + + + +.. figure:: figures/security_architecture.png + + + +Note: In above diagram n ( n is numeric) is used for cloud node and n’ for edge node. + + +FogFlow cloud node flow: + +1. As in architecture diagram, PEP Proxy will register itself on behalf FogFlow Designer first on Keyrock. Detail explanation is given in `below`_ topics of this tutorial. + +2. User can access Designer via PEP proxy proxy by using the access-token of PEP proxy in reaquest header. + +.. _`below`: https://fogflow.readthedocs.io/en/latest/https.html#setup-security-components-on-cloud-node + +FogFlow edge node flow: + + +1. On behalf of edge node, one application will be register on keyrock it will be using OAuth credentials. Detail explanation is given in below topics of this tutorial. Click `here`_ to refer. + +2. After the authentication edge node will be able to communicate with FogFlow cloud node. + +3. Any device can register itself or communicate with FogFlow edge node using edge’s OAuth access-token. + +.. _`here`: https://fogflow.readthedocs.io/en/latest/https.html#setup-components-on-edge + + +Installation +------------------ + + +.. code-block:: console + + + # the docker-compose file to start security components on the cloud node + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/core/http/docker-compose.idm.yml + + # the configuration file used by IdM + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/core/http/idm_config.js + + # the configuration file used by PEP Proxy + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/core/http/pep_config.js + + + +Change the IP configuration accordingly +------------------------------------------------------------- + +Configuration file need to be modified at the following places with IP addresses according to user own environment. + +- Change PEP Proxy host-port and container port in docker-compose.idm.yml file. + +- Change the IdM config file at following places as per the environment. + + +.. code-block:: console + + + config.port = 3000; + config.host = "http://:" + config.port; + + config.database = { + host: "localhost", + password: "idm", + username: "root", + database: "idm", + dialect: "mysql", + port: undefined + }; + + +Start all Security components: + +.. code-block:: console + + docker-compose -f docker-compose.idm.yml up -d + + #Check all the containers are Up and Running using "docker ps -a" + docker ps -a + + + +Setup security components on Cloud node +------------------------------------------------- + +Below are the steps that need to be done to setup communication between IdM components. + + +**Step1**: Authenticate PEP Proxy itself with Keyrock Identity Management. + + + +.. figure:: figures/keyrock_Account_Portal.png + + + +Login to Keyrock (http://180.179.214.135:3000/idm/) account with user credentials i.e. Email and Password. + For Example: admin@test.com and 1234. + +After Login, Click “Applications” then ”FogFLow PEP”. +Click “PEP Proxy” link to get Application ID , PEP Proxy Username and PEP Proxy Password. + +Note: +Application ID , PEP Proxy Username and PEP Proxy Password will generate by clicking ‘Register PEP Proxy’ button. + + +To setup PEP proxy for securing Designer, change the followings inside the pep_config file. Get PEP Proxy Credentials from Keyrock Dashboard while registering an application. Note that single instance of Wilma will be installed for each application that user wants to secure. + + +.. code-block:: console + + config.pep_port = process.env.PEP_PROXY_PORT || 80; + config.idm = { + host: process.env.PEP_PROXY_IDM_HOST || '180.179.214.135', + port: process.env.PEP_PROXY_IDM_PORT || 3000, + ssl: toBoolean(process.env.PEP_PROXY_IDM_SSL_ENABLED, false), + }; + config.app = { + host: process.env.PEP_PROXY_APP_HOST || '180.179.214.135', + port: process.env.PEP_PROXY_APP_PORT || ’80’, + ssl: toBoolean(process.env.PEP_PROXY_APP_SSL_ENABLED, false), // Use true if the app server listens in https + }; + + config.pep = { + app_id: process.env.PEP_PROXY_APP_ID || '9b51b184-808c-498c-8aac-74ffedc1ee72', + username: process.env.PEP_PROXY_USERNAME || 'pep_proxy_4abf36da-0936-46f9-a7f5-ac7edb7c86b6', + password: process.env.PEP_PASSWORD || 'pep_proxy_fb4955df-79fb-4dd7-8968-e8e60e4d6159', + token: { + secret: process.env.PEP_TOKEN_SECRET || '', // Secret must be configured in order validate a jwt + }, + trusted_apps: [], + }; + + +Restart the PEP Proxy container after above changes. + + +**Step2**: Request Keyrock IDM to generate access-token and refresh token. + + +1. Set the HTTP request Header, payload and Authorization field as per below screen shots. + +2. Click “Send” Button to get access-token. + + + +.. figure:: figures/detailDescriptionofSampleRequest.png + + + + +Note: Obtain Client ID and Client Secret from Keyrock dashboard under ‘Oauth2 Credentials’ + + + +.. figure:: figures/detailDescriptionofSampleRequest2.png + + + +Note: IoT sensor ID and Password can be obtained from keyrock dashboard + + + +.. figure:: figures/responseFromKeyrock.png + + + +The flow of cloud security implementation can be understand by below figure. + + + + +.. figure:: figures/architectureDiagram.png + + + + +Below are some points related to above architecture diagram: + +1. Access-token should be already known to user. + +2. For an application designer register a pep proxy to keyrock. + +3. Keyrock will send access-token to pep. + +4. Using that token user will send create entity request to designer. + +5. Designer will send token to keyrock to authenticate. + +6. Entity creation request will transfer to FogFlow. + + + +**entity Registration using token_access** + + +.. code-block:: console + + curl -iX POST 'http://:/ngsi10/updateContext' -H 'X-Auth-Token: ' -H 'Content-Type: application/json' + -d ' + { + "contextElements": [ + { + "entityId": { + "id": "Temperature100", + "type": "Temperature", + "isPattern": false + }, + "attributes": [ + { + "name": "temp", + "type": "float", + "value": 34 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": 49.406393, + "longitude": 8.684208 + } + } + ], + "updateAction": "UPDATE" + } + ] + }' + + +Setup components on Edge +------------------------- + + +FogFlow edge node mainly contains edge broker and edge worker. To secure FogFlow cloud-edge communication OAuth Token has been used. In order to create an OAuth Token, first need to register an application on Keyrock. So, a script will call with the start of edge node and it will register an application with the keyrock on behalf of edge node using the Keyrock APIs. The script will perform following steps: + +**Prerequisite** + +Two commands need to install before setup edge: + +1. Curl + +2. jq + + +scripts Installation +--------------------- + +Below scripts need to download for setting up edge node. + +.. code-block:: console + + #download the deployment scripts + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/edge/http/start.sh + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/edge/http/stop.sh + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/edge/http/script.sh + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/edge/http/delete.ah + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/edge/http/oauth_config.js + wget https://raw.githubusercontent.com/smartfog/fogflow/master/docker/edge/http/delete_config.js + + #make them executable + chmod +x script.sh start.sh stop.sh delete.sh + + +Change the IP configuration accordingly +------------------------------------------------------------- + +Chanage the following things in configuration file: + +* Change the oauth_config.js and add IdM IP, Application name, redirect_url and Url for the application that is need to be registered. + +**Start Edge node components** + +.. code-block:: console + + + #start components in the same script + ./start.sh + + + +To secure FogFlow cloud-edge communication OAuth Token has been used. In order to create an OAuth Token, + +* An application need to be registered on Keyrock. + +* A script will call with the start of edge node and it will register an application with the keyrock on behalf of that edge node using the Keyrock APIs. + + +Note: the start.sh script will return API token, Application ID its Secret and the access token on console. Please save these for further use. + + +register device on edge node +---------------------------- + +An example payload of registration device is given below. + +.. code-block:: console + + + Curl -iX POST 'http://:/NGSI9/registerContext' -H 'Content-Type: application/json' -H 'fiware-service: openiot' -H 'X-Auth-token: ' -H 'fiware-servicepath: /' -d ' + { + "contextRegistrations": [ + { + "entities": [ + { + "type": "Lamp", + "isPattern": "false", + "id": "Lamp.0020" + } + ], + "attributes": [ + { + "name": "on", + "type": "command" + }, + { + "name": "off", + "type": "command" + } + ], + "providingApplication": "http://0.0.0.0:8888" + } + ], + "duration": "P1Y" + }' + + +**Stop Edge node components** + + +* Change the delete_config.js and add Application ID(APP_ID) and IDM token (received while registering application) for the application that is need to be registered. + + +.. code-block:: console + + #stop all components in the same script + ./stop.sh diff --git a/doc/en/source/ngsi_ld_api_walkthrough.rst b/doc/en/source/ngsi_ld_api_walkthrough.rst new file mode 100644 index 00000000..f053181e --- /dev/null +++ b/doc/en/source/ngsi_ld_api_walkthrough.rst @@ -0,0 +1,792 @@ +***************************************** +NGSI-LD API Walkthrough +***************************************** + +This tutorial is focused mainly on the NGSI-LD APIs supported in FogFlow, which include APIs for entities, context registrations and subscriptions. These are discussed in more detail in the following sections. For using NGSI-LD APIs in FogFlow, checkout the latest docker image "fogflow/broker:3.1" from Docker Hub. + +FogFlow follows the NGSI-LD data-model, with continuous improvements. For better understanding of NGSI-LD Data-model, refer `this`_. + +.. _`this`: https://fiware-datamodels.readthedocs.io/en/latest/ngsi-ld_howto/index.html + + +Entities +========================= + +Entities are the units for representing objects in the environment, each having some properties of their own, may also have some relationships with others. This is how linked data are formed. + + +Create entities +------------------------------------------ + +There are several ways of creating an NGSI-LD Entity on FogFlow Broker: + +* When context is provided in the Link header: The context for resolving the payload is given through the Link header. +* When context is provided in the payload: Context is in the payload itself, there is no need to attach a Link header in the request. +* When the request payload is already expanded: Some payloads are already expanded using some context. + +Curl requests for creating an entity on FogFlow Broker in different ways are given below. All the are the POST requests to FogFlow Broker. Broker returns a response of "201 Created" for a successful creation of a new entity and "409 Conflict" on creating an already existing entity. + +**When context is provided in the Link header:** + +.. code-block:: console + + curl -iX POST \ + 'http://:8070/ngsi-ld/v1/entities/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"' \ + -d ' + { + "id": "urn:ngsi-ld:Vehicle:A100", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "BMW", + "observedAt": "2017-07-29T12:00:04" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 81, + "observedAt": "2017-07-29T12:00:04" + }, + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + }' + +**When context is provided in the payload:** + +.. code-block:: console + + curl -iX POST \ + 'http://:8070/ngsi-ld/v1/entities/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -d ' + { + "@context": [{ + "Vehicle": "https://uri.etsi.org/ngsi-ld/default-context/Vehicle", + "brandName": "https://uri.etsi.org/ngsi-ld/default-context/brandName", + "speed": "https://uri.etsi.org/ngsi-ld/default-context/speed", + "isParked": { + "@type": "@id", + "@id": "https://uri.etsi.org/ngsi-ld/default-context/isParked" + } + }], + "id": "urn:ngsi-ld:Vehicle:A200", + "type": "Vehicle1", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + }' + +**When the request payload is already expanded:** + +.. code-block:: console + + curl -iX POST \ + 'http://:8070/ngsi-ld/v1/entities/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -d ' + { + "https://uri.etsi.org/ngsi-ld/default-context/brandName": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/Property" + ], + "https://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": "Mercedes" + } + ] + } + ], + "https://uri.etsi.org/ngsi-ld/createdAt": [ + { + "@type": "https://uri.etsi.org/ngsi-ld/DateTime", + "@value": "2017-07-29T12:00:04" + } + ], + "@id": "urn:ngsi-ld:Vehicle:A300", + "https://uri.etsi.org/ngsi-ld/default-context/isParked": [ + { + "https://uri.etsi.org/ngsi-ld/hasObject": [ + { + "@id": "urn:ngsi-ld:OffStreetParking:Downtown1" + } + ], + "https://uri.etsi.org/ngsi-ld/observedAt": [ + { + "@type": "https://uri.etsi.org/ngsi-ld/DateTime", + "@value": "2017-07-29T12:00:04" + } + ], + "https://uri.etsi.org/ngsi-ld/default-context/providedBy": [ + { + "https://uri.etsi.org/ngsi-ld/hasObject": [ + { + "@id": "urn:ngsi-ld:Person:Bob" + } + ], + "@type": [ + "https://uri.etsi.org/ngsi-ld/Relationship" + ] + } + ], + "@type": [ + "https://uri.etsi.org/ngsi-ld/Relationship" + ] + } + ], + "https://uri.etsi.org/ngsi-ld/location": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/GeoProperty" + ], + "https://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": "{ \"type\":\"Point\", \"coordinates\":[ -8.5, 41.2 ] }" + } + ] + } + ], + "https://uri.etsi.org/ngsi-ld/default-context/speed": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/Property" + ], + "https://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": 80 + } + ] + } + ], + "@type": [ + "https://uri.etsi.org/ngsi-ld/default-context/Vehicle" + ] + }' + + +Update entities +----------------------------------------------- + +Entities can be updated by updating their attributes (properties and relationships) and the attributes can be updated in the following ways: + +* Add more attributes to the entity: More properties or relationships or both can be added to an existing entity. This is a POST http request to Broker to append more attributes to the entity. +* Update existing attributes of the entity: Existing properties or relationships or both can be updated for an entity. This is a PATCH http request to FogFlow Broker. +* Update specific attribute of the entity: Fields of an existing attribute can be updated for an entity. This update is also called partial update. This is also a PATCH request to the FogFlow Broker. + +FogFlow Broker returns "204 NoContent" on a successful attribute update, "404 NotFound" for a non-existing entity. While updating the attributes of an exiting entity, some of the attributes provided in the request payload may not exist. For such cases, FogFlow Broker return a "207 MultiStatus" error. + +Here are the curl requests for these Updates. + +**Add more attributes to the entity:** + +.. code-block:: console + + curl -iX PATCH \ + 'http://:8070/ngsi-ld/v1/entities//attrs' \ + -H 'Content-Type: application/ld+json' \ + -d ' + { + "@context": { + "brandName1": "https://uri.etsi.org/ngsi-ld/default-context/brandName1", + "isParked1": "https://uri.etsi.org/ngsi-ld/default-context/isParked1" + }, + "brandName1": { + "type": "Property", + "value": "Audi" + }, + + "isParked1": { + "type": "Relationship", + "object": "Audi" + } + }' + +**Update existing attributes of the entity:** + +.. code-block:: console + + curl -iX PATCH \ + 'http://:8070/ngsi-ld/v1/entities//attrs' \ + -H 'Content-Type: application/ld+json' \ + -d ' + { + "@context": { + "isParked": "https://uri.etsi.org/ngsi-ld/default-context/isParked" + }, + "brandName": { + "type": "Property", + "object": "Audi" + } + }' + +**Update specific attribute of the entity:** + +.. code-block:: console + + curl -iX PATCH \ + 'http://:8070/ngsi-ld/v1/entities//attrs/' \ + -H 'Content-Type: application/ld+json' \ + -d ' + { + "@context": { + "brandName": "https://uri.etsi.org/ngsi-ld/default-context/brandName" + }, + "value": "Suzuki" + }' + + +Get entities +----------------------------------------------- + +This section describes how to retrieve the already created entities from FogFlow Broker. Entities can be retrieved from FogFlow based on different filters, listed below. + +* Based on Entity Id: returns an entity whose id is passed in the request URL. +* Based on Attribute Name: returns all those entities which contain the attribute name that is passed in the query parameters of the request URL. +* Based on Entity Id and Entity Type: returns the entity with the entity id same as given in the query parameters along with the type matching. +* Based on Entity Type: returns all the entities that are of the requested type. +* Based on Entity Type with Link header: returns all the entities of requested type, but here the type can be given in a different way in the query parameters of request URL. Refer the request for this in the following sections. +* Based on Entity IdPattern and Entity Type: returns all those entities which lie inside the IdPattern range and the matching type mentioned in the query parameters. + +On successful retrieval of at least one entity in the above requests, FogFlow Broker returns a "200 OK" response. For non-existing entities, "404 NotFound" error is returned. + +**Based on Entity Id:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/entities/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + +**Based on Attribute Name:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/entities?attrs=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + +**Based on Entity Id and Entity Type:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/entities?id=&type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + +**Based on Entity Type:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/entities?type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + +**Based on Entity Type with Link header:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/entities?type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"' + +**Based on Entity IdPattern and Entity Type:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/entities?idPattern=&type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + + +Delete entities +----------------------------------------------- + +Either an entity can be deleted, or a specific attribute of that entity can be deleted. Successful deletion returns a "204 NoContent" response, while for non-existing attributes or entities, it returns "404 NotFound" error. + +**Deleting specific attribute of an entity:** + +.. code-block:: console + + curl -iX DELETE \ + 'http://:8070/ngsi-ld/v1/entities//attrs/' + +**Deleting an entity:** + +.. code-block:: console + + curl -iX DELETE \ + 'http://:8070/ngsi-ld/v1/entities/' + + +Registrations +================================ + +Registrations or C-Source Registrations are used to indicate which device will be feeding what data to a Broker. Data description like the entity ids and their types, their properties and relationships, endpoint of the provider, location of the data, etc. are given in a C-Source request. + + +Create registrations +-------------------------------------- + +A C-Source Registration can be created on FogFlow Broker in the following two ways: + +* Using context in Link header: context for resolving the request payload is contained in the Link header of the request. +* Using context in payload: context is given in the payload itself. + +On creating a C-Source registration, FogFlow Broker returns "201 Created" response, while in case of at least one already registered entity in the request payload, it will return a "409 Conflict" error. + +Curl requests are given in the following sections. + +**Using context in Link header:** + +.. code-block:: console + + curl -iX POST \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"' \ + -d ' + { + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3400", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A500", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A600", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://my.csource.org:1026", + "location": "{ \"type\": \"Polygon\", \"coordinates\": [[[8.686752319335938,49.359122687528746],[8.742027282714844,49.3642654834877],[8.767433166503904,49.398462568451485],[8.768119812011719,49.42750021620163],[8.74305725097656,49.44781634951542],[8.669242858886719,49.43754770762113],[8.63525390625,49.41968407776289],[8.637657165527344,49.3995797187007],[8.663749694824219,49.36851347448498],[8.686752319335938,49.359122687528746]]] }", + "timestamp": { + "start": "2017-11-29T14:53:15", + "end": "2017-12-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15" + }' + +**Using context in payload:** + +.. code-block:: console + + curl -iX POST \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -d ' + { + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3401", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A700", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A800", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://my.csource.org:1026", + "location": "{ \"type\": \"Polygon\", \"coordinates\": [[[8.686752319335938,49.359122687528746],[8.742027282714844,49.3642654834877],[8.767433166503904,49.398462568451485],[8.768119812011719,49.42750021620163],[8.74305725097656,49.44781634951542],[8.669242858886719,49.43754770762113],[8.63525390625,49.41968407776289],[8.637657165527344,49.3995797187007],[8.663749694824219,49.36851347448498],[8.686752319335938,49.359122687528746]]] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", + "@context": [ + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld", + { + "Vehicle": "https://uri.etsi.org/ngsi-ld/default-context/Vehicle", + "brandName": "https://uri.etsi.org/ngsi-ld/default-context/brandName", + "brandName1": "https://uri.etsi.org/ngsi-ld/default-context/brandName1", + "speed": "https://uri.etsi.org/ngsi-ld/default-context/speed", + "totalSpotNumber": "https://uri.etsi.org/ngsi-ld/default-context/parking/totalSpotNumber", + "reliability": "https://uri.etsi.org/ngsi-ld/default-context/reliability", + "OffStreetParking": "https://uri.etsi.org/ngsi-ld/default-context/parking/OffStreetParking", + "availableSpotNumber": "https://uri.etsi.org/ngsi-ld/default-context/parking/availableSpotNumber", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "isParked": { + "@type": "@id", + "@id": "https://uri.etsi.org/ngsi-ld/default-context/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "https://uri.etsi.org/ngsi-ld/default-context/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "https://uri.etsi.org/ngsi-ld/default-context/providedBy" + }, + "name": "https://uri.etsi.org/ngsi-ld/default-context/name" + } + ] + }' + + +Update registrations +-------------------------------------- + +An existing C-Source Registration can be updated by its id. Context for resolving the payload is given in the request payload. In case the context object is not given in the request payload, FogFlow Broker will resolve the payload using the default context. "204 NoContent" response is returned on a successful registration update on FogFlow Broker. + +Curl request for C-Source Registration update is given below. + +.. code-block:: console + + curl -iX PATCH \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -d ' + { + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A500", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed", + "brandName1" + ], + "relationships": [ + "isParked", + "isParked1" + ] + }, + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A600", + "type": "Vehicle" + } + ], + "properties": [ + "brandName" + ], + "relationships": [ + "isParked" + ] + } + ], + "endpoint": "http://my.csource.org:1026", + "location": "{ \"type\": \"Polygon\", \"coordinates\": [[[8.686752319335938,49.359122687528746],[8.742027282714844,49.3642654834877],[8.767433166503904,49.398462568451485],[8.768119812011719,49.42750021620163],[8.74305725097656,49.44781634951542],[8.669242858886719,49.43754770762113],[8.63525390625,49.41968407776289],[8.637657165527344,49.3995797187007],[8.663749694824219,49.36851347448498],[8.686752319335938,49.359122687528746]]] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", + "@context": [ + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld", + { + "Vehicle": "https://uri.etsi.org/ngsi-ld/default-context/Vehicle", + "brandName": "https://uri.etsi.org/ngsi-ld/default-context/brandName", + "brandName1": "https://uri.etsi.org/ngsi-ld/default-context/brandName1", + "speed": "https://uri.etsi.org/ngsi-ld/default-context/speed", + "totalSpotNumber": "https://uri.etsi.org/ngsi-ld/default-context/parking/totalSpotNumber", + "reliability": "https://uri.etsi.org/ngsi-ld/default-context/reliability", + "OffStreetParking": "https://uri.etsi.org/ngsi-ld/default-context/parking/OffStreetParking", + "availableSpotNumber": "https://uri.etsi.org/ngsi-ld/default-context/parking/availableSpotNumber", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "isParked": { + "@type": "@id", + "@id": "https://uri.etsi.org/ngsi-ld/default-context/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "https://uri.etsi.org/ngsi-ld/default-context/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "https://uri.etsi.org/ngsi-ld/default-context/providedBy" + }, + "name": "https://uri.etsi.org/ngsi-ld/default-context/name", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "expires":"http://uri.etsi.org/ngsi-ld/expires" + } + ] + }' + + +Get registrations +-------------------------------------- + +C-Source Registrations can be retrieved from FogFlow Broker using the following filters, which are passed in the request through query parameters. + +* Based on Entity Type: returns all the registrations with the matching entity type. +* Based on Entity Type with Link header: returns all the registrations with matching entity type, but here, entity type is passed differently. +* Based on Entity Id and Entity Type: returns the registration which contains the requested entity id and type. +* Based on Entity IdPattern and Entity Type: returns all those registrations which lie within the range of requested entity id pattern and also matching the entity type. + +Successful retrieval returns "200 OK" response while in case on not-existing registrations, Broker returns "404 NotFound" error. Send the following curl requests to Broker to view how it works. + +**Based on Entity Type:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations?type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + +**Based on Entity Type with Link header:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations?type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"' + +**Based on Entity Id and Entity Type:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations?id=&type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + +**Based on Entity IdPattern and Entity Type:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations?idPattern=&type=' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' + + +Delete registrations +-------------------------------------- + +C-Source registration can be deleted using the following request. + +.. code-block:: console + + curl -iX DELETE \ + 'http://:8070/ngsi-ld/v1/csourceRegistrations/' + + +Subscriptions +================================ + +Subscribers can subscribe for entities using a subscription request to the FogFlow Broker. + + +Create subscriptions +-------------------------------------- + +Subscriptions can be created either for an Entity Id or an Entity Id Pattern. Whenever entity update is there for that subscription, FogFlow Broker will automatically notify the updated entity to the subscribers. "201 Created" response is returned on a successful subscription on Broker, along with the Subscription Id, which can later be used to retrieve, update or delete the subscription. + +Refer the following curl requests, but before running the subscriptions, make sure some notify receiver is running, that can simply view the contents of the notification. For already subscribed entities, when entity creation or update takes place, a notification will be received by the subscriber. Notification is also received by a subscriber in case of subscription to an already existing entity. + +**Subscribing for an Entity Id** + +.. code-block:: console + + curl -iX POST \ + 'http://:8070/ngsi-ld/v1/subscriptions/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"' \ + -d ' + { + "type": "Subscription", + "entities": [{ + "id" : "urn:ngsi-ld:Vehicle:A100", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName"], + "notification": { + "attributes": ["brandName"], + "format": "keyValues", + "endpoint": { + "uri": "http://my.endpoint.org/notify", + "accept": "application/json" + } + } + }' + +**Subscribing for an IdPattern:** + +.. code-block:: console + + curl -iX POST \ + 'http://:8070/ngsi-ld/v1/subscriptions/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"' \ + -d ' + { + "type": "Subscription", + "entities": [{ + "idPattern" : ".*", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName"], + "notification": { + "attributes": ["brandName"], + "format": "keyValues", + "endpoint": { + "uri": "http://my.endpoint.org/notify", + "accept": "application/json" + } + } + }' + + +Update subscriptions +-------------------------------------- + +An existing subscription on FogFlow Broker can be updated by id using the curl request given below. + +.. code-block:: console + + curl -iX PATCH \ + 'http://:8070/ngsi-ld/v1/subscriptions/' \ + -H 'Content-Type: application/ld+json' \ + -H 'Accept: application/ld+json' \ + -H 'Link: <{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"' \ + -d ' + { + "type": "Subscription", + "entities": [{ + "type": "Vehicle1" + }], + "watchedAttributes": ["https://uri.etsi.org/ngsi-ld/default-context/brandName11"], + "notification": { + "attributes": ["https://uri.etsi.org/ngsi-ld/default-context/brandName223"], + "format": "keyValues", + "endpoint": { + "uri": "http://my.endpoint.org/notify", + "accept": "application/json" + } + } + }' + + +Get subscriptions +-------------------------------------- + +All the subscriptions or a subscription with specific id, both can be retrieved from FogFlow Broker with a response of "200 OK". Curl requests are given below. + +**All Subscriptions:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/subscriptions/' \ + -H 'Accept: application/ld+json' + +**Specific subscription:** + +.. code-block:: console + + curl -iX GET \ + 'http://:8070/ngsi-ld/v1/subscriptions/' \ + -H 'Accept: application/ld+json' + + +Delete subscriptions +-------------------------------------- + +A subscription can be deleted by sending the following request to FogFlow Broker, with a response of 204 "NoContent". + +.. code-block:: console + + curl -iX DELETE \ + 'http://:8070/ngsi-ld/v1/subscriptions/' + + + +**The NGSI-LD support in FogFlow also carries some limitations with it. Improvements are continued.** diff --git a/doc/en/source/onepage.rst b/doc/en/source/onepage.rst index c1581b42..3890875d 100644 --- a/doc/en/source/onepage.rst +++ b/doc/en/source/onepage.rst @@ -8,6 +8,18 @@ The tutorial introduces a typical FogFlow system setup with a simple example to data. It explains an example usecase implementation using FogFlow and FIWARE Orion in integration with each other. +Every time to implement a usecase FogFlow creates some internal NGSI entities such as operators, Docker image, Fog Function and service topology. +So these entity data are very important for FogFlow system and these are need to be stored somewhere. An entity data can not be stored in FogFlow memory +because memory is volatile and it will lose content when power is lost. To solve this issue FogFlow introduces `Dgraph`_ a persistent storage. +The persistent storage will store FogFlow entity data in the form of graph. + + + +.. _`Dgraph`: https://dgraph.io/docs/get-started/ + + + + As shown in the following diagram, in this use case a connected temperature sensor sends an update message to the FogFlow system, which triggers some running task instance of a pre-defined fog function to generate some analytics result. The fog function is specified in advance via the FogFlow dashboard, diff --git a/doc/en/source/system_overview.rst b/doc/en/source/system_overview.rst index 577fe778..b31a9aac 100644 --- a/doc/en/source/system_overview.rst +++ b/doc/en/source/system_overview.rst @@ -14,6 +14,85 @@ illustrated in below figure. All integrated features that are running in FogFlow .. figure:: figures/FogFlow_System_Design.png + +FogFlow is now supporting graph database to store internal entity data for later use. In FogFlow system, the interaction point of graph database is designer. +So FogFlow entity that create either by FogFlow web browser or via any other client such as curl or python client, +the entity creation request directly goes to Designer. Then Designer will send this entity request on cloud broker to +register the entity and at the same time Designer will send this data on DGraph to store it into database. + +Whenever FogFlow system will restart, Designer will trigger a query request to DGraph and get all the stored entities +from DGraph then send these entities to cloud broker to register them. + +.. note:: FogFlow Designer is dependent on Discovery and Cloud Broker. + +The integration of FogFlow Designer and DGraph is done via gRPC, where a Dgraph client is implemented with Designer server as in below code +using gRPC. Default port of gRPC is 9080, in FogFlow 9082 port is used. + +.. code-block:: console + + /* + creating grpc client for making connection with dgraph + */ + function newClientStub() { + return new dgraph.DgraphClientStub(config.HostIp+":"+config.grpcPort, grpc.credentials.createInsecure()); + } + + // Create a client. + function newClient(clientStub) { + return new dgraph.DgraphClient(clientStub); + } + + +Whenever new FogFlow entity gets created by web browser the request goes to designer or user directly creates entity on designer using any client. Then designer perform two tasks: + +1. Send request to cloud broker to register the entity. + +2. Calls DGraph client to store entity data, the DGraph client will create a connection with dgraph server after that create schema and then send data to DGraph. Apart from this, one another flow will be triggered from designer when FogFlow system will restart. In this flow designer will query all stored entity data from DGraph and forward to cloud broker for registering these entites. + +Below the glimpse of code to create schema and insert data into DGraph. + +.. code-block:: console + + /* + create schema for node + */ + async function setSchema(dgraphClient) { + const schema = ` + attributes: [uid] . + domainMetadata: [uid] . + entityId: uid . + updateAction: string . + id: string . + isPattern: bool . + latitude: float . + longitude: float . + name: string . + type: string . + value: string . + `; + const op = new dgraph.Operation(); + op.setSchema(schema); + await dgraphClient.alter(op); + } + + /* + insert data into database + */ + async function createData(dgraphClient,ctx) { + const txn = dgraphClient.newTxn(); + try { + const mu = new dgraph.Mutation(); + mu.setSetJson(ctx); + const response = await txn.mutate(mu); + await txn.commit(); + } + finally { + await txn.discard(); + } + } + +For detailed code refer https://github.com/smartfog/fogflow/blob/development/designer/dgraph.js + In this page, a brief introduction is given about FogFlow integrations, for more detailed information refer links. @@ -32,6 +111,16 @@ to enable FogFlow Ecosystem to interact with Scorpio context broker. The NGSI-LD .. _`Integrate FogFlow with Scorpio Broker`: https://fogflow.readthedocs.io/en/latest/scorpioIntegration.html +**FogFlow NGSI-LD Support** FogFlow is providing NGSI-LD API support along with NGSI9 and NGSI10. +NGSI-LD format aims at utilizing the **linked data model used by Fiware** for the purpose of unambiguous and better understanding of context sharing communication among components of fogflow or other GE's. It reduces the complexity of maintaining the data among NGSIv1 and NGSIv2 model by establishing a relationship between data to deduce information in a more efficient manner. + +- The reason for incorporating this model is the direct need of linked data association that are forming the backbone of Edge Computing. +- this bridges the gap between Fogflow and other GE's, since this has made possible to interact among each other, like the interaction among Fogflow and Scorpio Broker. +Detail about NGSI-LD APIs can be checked via `API Walkthrough`_ page. + +.. _`API Walkthrough`: https://fogflow.readthedocs.io/en/latest/api.html#ngsi-ld-supported-apis + + FogFlow can also be Integrated with Orion context broker using NGSI APIs. More detail can be checked via `Integrate FogFlow with FIWARE`_ page. diff --git a/docker/core/http/config.json b/docker/core/http/config.json index 77aef9ed..53c4b993 100644 --- a/docker/core/http/config.json +++ b/docker/core/http/config.json @@ -1,7 +1,7 @@ { - "coreservice_ip": "10.156.0.9", - "external_hostip": "10.156.0.9", - "internal_hostip": "172.17.0.1", + "coreservice_ip": "127.0.0.1", + "external_hostip": "127.0.0.1", + "internal_hostip": "127.0.0.1", "physical_location":{ "longitude": 139.709059, "latitude": 35.692221 @@ -38,5 +38,8 @@ }, "https": { "enabled" : false + }, + "persistent_storage": { + "port": 9082 } } diff --git a/docker/core/http/docker-compose.idm.yml b/docker/core/http/docker-compose.idm.yml new file mode 100644 index 00000000..d31a4b67 --- /dev/null +++ b/docker/core/http/docker-compose.idm.yml @@ -0,0 +1,46 @@ +version: "2" + +networks: + idm_network: + driver: bridge + ipam: + config: + - subnet: 172.18.1.0/24 + gateway: 172.18.1.1 + +volumes: + vol-mysql: + +services: + mysql: + image: mysql/mysql-server:5.7.21 + ports: + - "3306:3306" + networks: + idm_network: + ipv4_address: 172.18.1.5 + volumes: + - vol-mysql:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=idm + - MYSQL_ROOT_HOST=172.18.1.6 + + fiware-idm: + image: fiware/idm + ports: + - "3000:3000" + - "443:443" + networks: + idm_network: + ipv4_address: 172.18.1.6 + environment: + - IDM_DB_HOST=mysql + volumes: + - ./idm_config.js:/opt/fiware-idm/config.js + + pep-proxy: + image: fiware/idm + ports: + - [host_port]:[container_port] + volumes: + - ./pep_config.js:/opt/fiware-pep-proxy/config.js diff --git a/docker/core/http/docker-compose.yml b/docker/core/http/docker-compose.yml index 9c02a057..80f2ed65 100644 --- a/docker/core/http/docker-compose.yml +++ b/docker/core/http/docker-compose.yml @@ -1,27 +1,30 @@ version: "3.5" services: designer: - image: fogflow/designer:3.0 + image: fogflow/designer:3.2 volumes: - - ./config.json:/app/config.json + - ./config.json:/app/config.json ports: - 8080:8080 - 1030:1030 depends_on: - discovery - cloud_broker + - dgraph restart: always discovery: - image: fogflow/discovery:3.0 + image: fogflow/discovery:3.2 volumes: - ./config.json:/config.json ports: - 8090:8090 + depends_on: + - dgraph restart: always master: - image: fogflow/master:3.0 + image: fogflow/master:3.2 volumes: - ./config.json:/config.json links: @@ -31,21 +34,23 @@ services: depends_on: - rabbitmq - discovery - - cloud_broker + - cloud_broker + - dgraph restart: always cloud_broker: - image: fogflow/broker:3.0 + image: fogflow/broker:3.2 volumes: - ./config.json:/config.json ports: - 8070:8070 depends_on: - discovery + - dgraph restart: always cloud_worker: - image: fogflow/worker:3.0 + image: fogflow/worker:3.2 volumes: - /tmp:/tmp - ./config.json:/config.json @@ -76,6 +81,17 @@ services: - designer restart: always +#docker implementation for dgraph + dgraph: + image: fogflow/dgraph:latest + volumes: + - /mnt/dgraph:/dgraph + ports: + - 6080:6080 + - 8082:8080 + - 9082:9080 + - 8000:8000 + # Change the environment variables IOTA_CB_HOST and IOTA_PROVIDER_URL to run IoT Agent on Fogflow Cloud Node. # IoT Agent will use embedded mongodb, i.e., mongodb will be running on localhost. iot-agent: diff --git a/docker/core/http/idm_config.js b/docker/core/http/idm_config.js new file mode 100644 index 00000000..b0f89e0a --- /dev/null +++ b/docker/core/http/idm_config.js @@ -0,0 +1,181 @@ +const config = {}; + +function to_boolean(env, default_value){ + return (env !== undefined) ? (env.toLowerCase() === 'true') : default_value; +} + +function to_array(env, default_value){ + return (env !== undefined) ? env.split(',') : default_value; +} + +config.port = (process.env.IDM_PORT || 3000 ); +config.host = (process.env.IDM_HOST || '180.179.214.135' + config.port); + +config.debug = to_boolean(process.env.IDM_DEBUG, true); + +// HTTPS enable +config.https = { + enabled: to_boolean(process.env.IDM_HTTPS_ENABLED, false), + cert_file: 'certs/idm-2018-cert.pem', + key_file: 'certs/idm-2018-key.pem', + ca_certs: [], + port: (process.env.IDM_HTTPS_PORT || 443 ) +}; + +// Config email list type to use domain filtering +config.email_list_type = (process.env.IDM_EMAIL_LIST || null ); // whitelist or blacklist + +// Secret for user sessions in web +config.session = { + secret: (process.env.IDM_SESSION_SECRET || require('crypto').randomBytes(20).toString('hex')), // Must be changed + expires: (process.env.IDM_SESSION_DURATION || 60 * 60 * 1000) // 1 hour +} + +// Key to encrypt user passwords +config.password_encryption = { + key: (process.env.IDM_ENCRYPTION_KEY || 'nodejs_idm') // Must be changed +} + +// Enable CORS +config.cors = { + enabled: to_boolean(process.env.IDM_CORS_ENABLED, false), + options: { + /* eslint-disable snakecase/snakecase */ + origin: to_array(process.env.IDM_CORS_ORIGIN, '*'), + methods: to_array(process.env.IDM_CORS_METHODS, ['GET','HEAD','PUT','PATCH','POST','DELETE']), + allowedHeaders: (process.env.IDM_CORS_ALLOWED_HEADERS || '*'), + exposedHeaders: (process.env.IDM_CORS_EXPOSED_HEADERS || undefined), + credentials: (process.env.IDM_CORS_CREDENTIALS || undefined), + maxAge: (process.env.IDM_CORS_MAS_AGE || undefined), + preflightContinue: (process.env.IDM_CORS_PREFLIGHT || false), + optionsSuccessStatus: (process.env.IDM_CORS_OPTIONS_STATUS || 204) + /* eslint-enable snakecase/snakecase */ + } +} + +// Config oauth2 parameters +config.oauth2 = { + allow_empty_state: (process.env.IDM_OAUTH_EMPTY_STATE || false), // allow empty state in request + authorization_code_lifetime: (process.env.IDM_OAUTH_AUTH_LIFETIME || 5 * 60), // Five minutes + access_token_lifetime: (process.env.IDM_OAUTH_ACC_LIFETIME || 60 * 60), // One hour + ask_authorization: (process.env.IDM_OAUTH_ASK_AUTH || true), // Prompt a message to users to allow the application to read their details + refresh_token_lifetime: (process.env.IDM_OAUTH_REFR_LIFETIME || 60 * 60 * 24 * 14), // Two weeks + unique_url: (process.env.IDM_OAUTH_UNIQUE_URL || false) // This parameter allows to verify that an application with the same url + // does not exist when creating or editing it. If there are already applications + // with the same URL, they should be changed manually +} + +// Config api parameters +config.api = { + token_lifetime: (process.env.IDM_API_LIFETIME || 60*60) // One hour +} + +// Configure Policy Decision Point (PDP) +// - IdM can perform basic policy checks (HTTP verb + path) +// - AuthZForce can perform basic policy checks as well as advanced +// If authorization level is advanced you can create rules, HTTP verb+resource and XACML advanced. In addition +// you need to have an instance of authzforce deployed to perform advanced authorization request from a Pep Proxy. +// If authorization level is basic, only HTTP verb+resource rules can be created +config.authorization = { + level: (process.env.IDM_PDP_LEVEL || 'basic'), // basic|advanced + authzforce: { + enabled: to_boolean(process.env.IDM_AUTHZFORCE_ENABLED, false), + host: (process.env.IDM_AUTHZFORCE_HOST || 'localhost'), + port: (process.env.IDM_AUTHZFORCE_PORT|| 8080), + } +} + +// Enable usage control and configure where is the Policy Translation Point +config.usage_control = { + enabled: to_boolean(process.env.IDM_USAGE_CONTROL_ENABLED, false), + ptp: { + host: (process.env.IDM_PTP_HOST || 'localhost'), + port: (process.env.IDM_PTP_PORT|| 8080), + } +} + +// Database info +config.database = { + host: (process.env.IDM_DB_HOST || 'localhost'), + password: (process.env.IDM_DB_PASS || 'idm'), + username: (process.env.IDM_DB_USER || 'root'), + database: (process.env.IDM_DB_NAME || 'idm'), + dialect: (process.env.IDM_DB_DIALECT || 'mysql'), + port: (process.env.IDM_DB_PORT || undefined) +}; + +// External user authentication +config.external_auth = { + enabled: (process.env.IDM_EX_AUTH_ENABLED || false ), + id_prefix: (process.env.IDM_EX_AUTH_ID_PREFIX || 'external_'), + password_encryption: (process.env.IDM_EX_AUTH_PASSWORD_ENCRYPTION || 'sha1'), // bcrypt, sha1 and pbkdf2 supported + password_encryption_key: (process.env.IDM_EX_AUTH_PASSWORD_ENCRYPTION_KEY || undefined), + password_encryption_opt: { + digest: (process.env.IDM_EX_AUTH_PASSWORD_ENCRYPTION_OPT_DIGEST || 'sha256'), + keylen: (process.env.IDM_EX_AUTH_PASSWORD_ENCRYPTION_OPT_KEYLEN || 64), + iterations: (process.env.IDM_EX_AUTH_PASSWORD_ENCRYPTION_OPT_ITERATIONS || 27500) + }, + database: { + host: (process.env.IDM_EX_AUTH_DB_HOST ||'localhost'), + port: (process.env.IDM_EX_AUTH_PORT || undefined), + database: (process.env.IDM_EX_AUTH_DB_NAME ||'db_name'), + username: (process.env.IDM_EX_AUTH_DB_USER || 'db_user'), + password: (process.env.IDM_EX_AUTH_DB_PASS ||'db_pass'), + user_table: (process.env.IDM_EX_AUTH_DB_USER_TABLE ||'user_view'), + dialect: (process.env.IDM_EX_AUTH_DIALECT || 'mysql') + } +} + +// Email configuration +config.mail = { + transport: (process.env.IDM_EMAIL_TRANSPORT || 'smtp'), + domain: (process.env.IDM_EMAIL_DOMAIN || ''), + host: (process.env.IDM_EMAIL_HOST || 'localhost'), + port: (process.env.IDM_EMAIL_PORT || 25), + from: (process.env.IDM_EMAIL_ADDRESS || 'noreply@localhost'), + mailgun_api_key: (process.env.IDM_MAILGUN_API_KEY || '') +} + +// Config themes +config.site = { + title: (process.env.IDM_TITLE || 'Identity Manager'), + theme: (process.env.IDM_THEME || 'default') +}; + +// Config eIDAS Authentication +config.eidas = { + enabled: to_boolean(process.env.IDM_EIDAS_ENABLED, false), + gateway_host: (process.env.IDM_EIDAS_GATEWAY_HOST || 'localhost'), + node_host: (process.env.IDM_EIDAS_NODE_HOST || 'https://se-eidas.redsara.es/EidasNode/ServiceProvider'), + metadata_expiration: (process.env.IDM_EIDAS_METADATA_LIFETIME || 60 * 60 * 24 * 365) // One year +} + +// Enables the possibility of adding identity attributes in users' profile +config.identity_attributes = { + /* eslint-disable snakecase/snakecase */ + enabled: false, + attributes: [ + {name: 'Vision', key: 'vision', type: 'number', minVal: '0', maxVal: '100'}, + {name: 'Color Perception', key: 'color', type: 'number', minVal: '0', maxVal: '100'}, + {name: 'Hearing', key: 'hearing', type: 'number', minVal: '0', maxVal: '100'}, + {name: 'Vocal Capability', key: 'vocal', type: 'number', minVal: '0', maxVal: '100'}, + {name: 'Manipulation Strength', key: 'manipulation', type: 'number', minVal: '0', maxVal: '100'}, + {name: 'Reach', key: 'reach', type: 'number', minVal: '0', maxVal: '100'}, + {name: 'Cognition', key: 'cognition', type: 'number', minVal: '0', maxVal: '100'} + ] + /* eslint-enable snakecase/snakecase */ +} + + +if (config.session.secret === 'nodejs_idm' || config.password_encryption.key === 'nodejs_idm'){ + /* eslint-disable no-console */ + console.log('****************'); + console.log('WARNING: The current encryption keys match the defaults found in the plaintext'); + console.log(' template file - please update for a production instance'); + console.log('****************'); + /* eslint-enable no-console */ + +} + + +module.exports = config; diff --git a/docker/core/http/lcn/config.json b/docker/core/http/lcn/config.json index 84ab22ff..c4a08478 100644 --- a/docker/core/http/lcn/config.json +++ b/docker/core/http/lcn/config.json @@ -1,7 +1,7 @@ { - "coreservice_ip": "host.docker.internal", - "external_hostip": "host.docker.internal", - "internal_hostip": "host.docker.internal", + "coreservice_ip": "127.0.0.1", + "external_hostip": "127.0.0.1", + "internal_hostip": "127.0.0.1", "physical_location":{ "longitude": 139, "latitude": 35 diff --git a/docker/core/http/local/config.json b/docker/core/http/local/config.json index 84ab22ff..f15e351b 100644 --- a/docker/core/http/local/config.json +++ b/docker/core/http/local/config.json @@ -1,7 +1,7 @@ { - "coreservice_ip": "host.docker.internal", - "external_hostip": "host.docker.internal", - "internal_hostip": "host.docker.internal", + "coreservice_ip": "127.0.0.1", + "external_hostip": "127.0.0.1", + "internal_hostip": "127.0.0.1", "physical_location":{ "longitude": 139, "latitude": 35 @@ -14,7 +14,7 @@ "debug": "stdout" }, "discovery": { - "http_port": 8090 + "http_port": 8060 }, "broker": { "http_port": 8070 @@ -38,5 +38,8 @@ }, "https": { "enabled" : false - } + }, + "persistent_storage": { + "port": 9082 + } } diff --git a/docker/core/http/local/docker-compose.yml b/docker/core/http/local/docker-compose.yml index 7abfd6ba..a34e0a1a 100644 --- a/docker/core/http/local/docker-compose.yml +++ b/docker/core/http/local/docker-compose.yml @@ -1,23 +1,24 @@ version: "3" services: designer: - image: fogflow/designer + image: fogflow/designer:latest volumes: - - ./config.json:/app/config.json + - ./config.json:/app/config.json ports: - 8080:8080 - 1030:1030 depends_on: - discovery - cloud_broker + - dgraph restart: always - + discovery: image: fogflow/discovery volumes: - ./config.json:/config.json ports: - - 8090:8090 + - 8060:8060 restart: always master: @@ -70,5 +71,14 @@ services: depends_on: - discovery - cloud_broker - - designer - restart: always \ No newline at end of file + restart: always + + dgraph: + image: fogflow/dgraph:latest + volumes: + - ./dgraph:/dgraph + ports: + - 6080:6080 + - 8082:8080 + - 9082:9080 + - 8000:8000 \ No newline at end of file diff --git a/docker/core/http/local/nginx.conf b/docker/core/http/local/nginx.conf index 3932dc4e..4205343d 100644 --- a/docker/core/http/local/nginx.conf +++ b/docker/core/http/local/nginx.conf @@ -8,11 +8,11 @@ http { server_name www.fogflow.io; location / { - proxy_pass http://designer:8080/; + proxy_pass http://host.docker.internal:8080/; } location /ngsi9/ { - proxy_pass http://discovery:8090/ngsi9/; + proxy_pass http://discovery:8060/ngsi9/; } location /ngsi10/ { diff --git a/docker/core/http/pep_config.js b/docker/core/http/pep_config.js new file mode 100644 index 00000000..1d07d78a --- /dev/null +++ b/docker/core/http/pep_config.js @@ -0,0 +1,81 @@ +const config = {}; + +function toBoolean(env, defaultValue) { + return env !== undefined ? env.toLowerCase() === 'true' : defaultValue; +} + +function to_array(env, default_value){ + return (env !== undefined) ? env.split(',') : default_value; +} + +// Used only if https is disabled +config.pep_port = process.env.PEP_PROXY_PORT || 80; + +// Set this var to undefined if you don't want the server to listen on HTTPS +config.https = { + enabled: toBoolean(process.env.PEP_PROXY_HTTPS_ENABLED, false), + cert_file: 'cert/cert.crt', + key_file: 'cert/key.key', + port: process.env.PEP_PROXY_HTTPS_PORT || 443, +}; + +//IDM configration +config.idm = { + host: process.env.PEP_PROXY_IDM_HOST || 'localhost', + port: process.env.PEP_PROXY_IDM_PORT || 3000, + ssl: toBoolean(process.env.PEP_PROXY_IDM_SSL_ENABLED, false), +}; + +//Apllication information, for which account has been registered on IDM +config.app = { + host: process.env.PEP_PROXY_APP_HOST || 'localhost', + port: process.env.PEP_PROXY_APP_PORT || '80', + ssl: toBoolean(process.env.PEP_PROXY_APP_SSL_ENABLED, false), // Use true if the app server listens in https +}; + +config.organizations = { + enabled: toBoolean(process.env.PEP_PROXY_ORG_ENABLED, false), + header: process.env.PEP_PROXY_ORG_HEADER || 'fiware-service' +} + +// Credentials obtained when registering PEP Proxy in app_id in Account Portal +config.pep = { + app_id: process.env.PEP_PROXY_APP_ID || '', + username: process.env.PEP_PROXY_USERNAME || '', + password: process.env.PEP_PASSWORD || '', + token: { + secret: process.env.PEP_TOKEN_SECRET || '', // Secret must be configured in order validate a jwt + }, + trusted_apps: [], +}; + +// in seconds +config.cache_time = 31557600; + +// if enabled PEP checks permissions in two ways: +// - With IdM: only allow basic authorization +// - With Authzforce: allow basic and advanced authorization. +// For advanced authorization, you can use custom policy checks by including programatic scripts +// in policies folder. An script template is included there +// +// This is only compatible with oauth2 tokens engine + +config.authorization = { + enabled: toBoolean(process.env.PEP_PROXY_AUTH_ENABLED, false), + pdp: process.env.PEP_PROXY_PDP || 'idm', // idm|authzforce + azf: { + protocol: process.env.PEP_PROXY_AZF_PROTOCOL || 'http', + host: process.env.PEP_PROXY_AZF_HOST || 'localhost', + port: process.env.PEP_PROXY_AZF_PORT || 8080, + custom_policy: process.env.PEP_PROXY_AZF_CUSTOM_POLICY || undefined, // use undefined to default policy checks (HTTP verb + path). + }, +}; + +// list of paths that will not check authentication/authorization +// example: ['/public/*', '/static/css/'] +config.public_paths = to_array(process.env.PEP_PROXY_PUBLIC_PATHS, []); + +config.magic_key = process.env.PEP_PROXY_MAGIC_KEY || undefined; + +module.exports = config; + diff --git a/docker/edge/http/delete.sh b/docker/edge/http/delete.sh new file mode 100755 index 00000000..b65b73d4 --- /dev/null +++ b/docker/edge/http/delete.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +#Read Application ID and IDM Token from delete_config.js file +App_Id=`cat $(pwd)/delete_config.js | grep "APP_ID" | cut -f 2 -d "=" | sed 's/ //g' | tr -d '\n'` +App_token=`cat $(pwd)/delete_config.js | grep "IDM_TOKEN" | cut -f 2 -d "=" | sed 's/ //g' | tr -d '\n'` + +#curl request to delete application from Keyrock +curl -X DELETE "http://localhost:3000/v1/applications/$App_Id" -H "X-Auth-token:$App_token" diff --git a/docker/edge/http/delete_config.js b/docker/edge/http/delete_config.js new file mode 100644 index 00000000..a596ca2b --- /dev/null +++ b/docker/edge/http/delete_config.js @@ -0,0 +1,7 @@ +//This file is containing run time informations, required in deleteing application from keyrock when edge node will stop + +//Application ID of registered application +APP_ID=e2e8849e-659c-49bd-bde5-a7817fc93933 + +//IDM token, received during application registration +IDM_TOKEN=6975ab93-6329-478b-8d07-ae73c67722b3 diff --git a/docker/edge/http/oauth_config.js b/docker/edge/http/oauth_config.js new file mode 100644 index 00000000..1e18f4ff --- /dev/null +++ b/docker/edge/http/oauth_config.js @@ -0,0 +1,14 @@ +//This file is containing run time informations that is required in setting up security over edge node + +//Keyrock IP address +IDM_IP:"180.179.214.202" + +//Name of the application, using this name keyrock will register the application +Application_Name:"Test" + +//Redirect URI or callback URL, The user agent will be redirected to this URL when OAuth flow is finished. +Redirect_uri:"http://180.179.214.202:80/index.html" + +//For security purposes, only OAuth requests coming from this URL will be accepted by KeyRock. +Url:"http:180.179.214.202:80" + diff --git a/docker/edge/http/script.sh b/docker/edge/http/script.sh new file mode 100755 index 00000000..8cd07146 --- /dev/null +++ b/docker/edge/http/script.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +#Check if Curl command is present or not, If not present then install +if command -v curl >/dev/null +then + continue +else + echo "curl could not be found" + apt-get install curl -y &> /dev/null; +fi +#Check if jq command is present or not, If not present then install +if command -v jq >/dev/null +then + continue +else + echo "jq could not be found" + apt-get install jq -y &> /dev/null +fi +#Read the Keyrock IP, Application name, redirect_uri and application URL from oauth_config.js file +IP_ADDRESS=`cat $(pwd)/oauth_config.js | grep "IDM_IP" | cut -f 2 -d ":" | sed 's/"//g' | tr -d '\n'` + +appName=`cat $(pwd)/oauth_config.js | grep "Application_Name" | cut -d ":" -f 2- | sed 's/"//g' | tr -d '\n'` + +callbackUrl=`cat $(pwd)/oauth_config.js | grep "Redirect_uri" | cut -d ":" -f 2- | sed 's/"//g' | tr -d '\n'` + +appUri=`cat $(pwd)/oauth_config.js | grep "Url" | cut -d ":" -f 2- | sed 's/"//g' | tr -d '\n'` + +#Generate API token, It will return X-Subject-Token taht will further use for application register +#The curl command will retry thrice to reach to server, if packet drops in previous attempts +for connect in 1 2 3 +do + curl --include \ + --request POST \ + --header "Content-Type: application/json" \ + --data-binary "{ + \"name\": \"admin@test.com\", + \"password\": \"1234\" + }" \ + "http://$IP_ADDRESS:3000/v1/auth/tokens" > generate_token.txt + if [ $? -eq 0 ]; then + break + else + echo reconnecting... + continue + fi +done + +token=`grep "X-Subject-Token" generate_token.txt | cut -f 2 -d ":" | sed 's/\r$//g' | tr -d '\n'` +echo ----------------------------- +echo IDM token is $token +echo ----------------------------- + +#Register application, it will return Application ID and Password. ID and Password will use to genenrate access token +for connect1 in 1 2 3 +do + curl -sb --include --request POST -H "Content-Type: application/json" -H "X-Auth-token: $token" --data-binary "{ + \"application\": { + \"name\": \"$appName\", + \"description\": \"description\", + \"redirect_uri\": \"$callbackUrl\", + \"url\": \"$appUri\", + \"grant_type\": [ + \"authorization_code\", + \"implicit\", + \"password\" + ], + \"token_types\": [ + \"jwt\", + \"permanent\" + ] + } + }" \ + "http://$IP_ADDRESS:3000/v1/applications" > accessAPI.txt + if [ $? -eq 0 ]; then + break + else + echo reconnecting... + continue + fi +done + +ID=`cat accessAPI.txt | tail -1 | jq . | grep "id" | cut -f 2 -d ":" | sed -e 's/"//g' -e 's/,//g' -e 's/\r$//g' -e 's/ //g' | tr -d '\n'` +SECRET=`cat accessAPI.txt | tail -1 |jq . | grep -m 1 "secret" | cut -f 2 -d ":" | sed -e 's/"//g' -e 's/,//g' -e 's/\r$//g' -e 's/ //g' | tr -d '\n'` + + +#Create access token with Resource Owner Password credentials, received from above step +echo ----------------------------- +echo App ID and Passwords are $ID,$SECRET +echo ----------------------------- + +for connect2 in 1 2 3 +do + curl -X POST -H "Authorization: Basic $(echo -n $ID:$SECRET | base64 -w 0)" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=password&username=admin@test.com&password=1234" "http://$IP_ADDRESS:3000/oauth2/token" > access_token.txt +if [ $? -eq 0 ]; then + break + else + echo reconnecting... + continue + fi +done +#cat access_token.txt +access=`cat access_token.txt |jq . | grep "access_token" | cut -d ":" -f2 | sed -e 's/ //g' -e 's/"//g' -e 's/,//g' -e 's/\\n//g' | tr -d '\n'` +echo ----------------------------- +echo " access token is" $access +echo ----------------------------- + +echo "end of script" +rm -rf accessAPI.txt access_token.txt generate_token.txt + + diff --git a/docker/edge/http/start.sh b/docker/edge/http/start.sh index 2243cb47..0f2f329e 100755 --- a/docker/edge/http/start.sh +++ b/docker/edge/http/start.sh @@ -4,7 +4,12 @@ else htype='arm' fi -docker run -d --name=metricbeat --user=root --volume="$(pwd)/metricbeat.docker.yml:/usr/share/metricbeat/metricbeat.yml:ro" --volume="/var/run/docker.sock:/var/run/docker.sock:ro" --volume="/sys/fs/cgroup:/hostfs/sys/fs/cgroup:ro" --volume="/proc:/hostfs/proc:ro" --volume="/:/hostfs:ro" docker.elastic.co/beats/metricbeat:7.6.0 metricbeat -e -E output.elasticsearch.hosts=[":9200"] -docker run -d --name=edgebroker -v $(pwd)/config.json:/config.json -p 8170:8170 fogflow/broker:$htype -docker run -d --name=edgeworker -v $(pwd)/config.json:/config.json -v /tmp:/tmp -v /var/run/docker.sock:/var/run/docker.sock fogflow/worker:$htype +sh $(pwd)/script.sh +if [ $? -eq 0 ]; then + docker run -d --name=metricbeat --user=root --volume="$(pwd)/metricbeat.docker.yml:/usr/share/metricbeat/metricbeat.yml:ro" --volume="/var/run/docker.sock:/var/run/docker.sock:ro" --volume="/sys/fs/cgroup:/hostfs/sys/fs/cgroup:ro" --volume="/proc:/hostfs/proc:ro" --volume="/:/hostfs:ro" docker.elastic.co/beats/metricbeat:7.6.0 metricbeat -e -E output.elasticsearch.hosts=[":9200"] + docker run -d --name=edgebroker -v $(pwd)/config.json:/config.json -p 8170:8170 fogflow/broker:$htype + docker run -d --name=edgeworker -v $(pwd)/config.json:/config.json -v /tmp:/tmp -v /var/run/docker.sock:/var/run/docker.sock fogflow/worker:$htype +else + echo failed security check +fi diff --git a/docker/edge/http/stop.sh b/docker/edge/http/stop.sh index e972ad84..65a1d49d 100755 --- a/docker/edge/http/stop.sh +++ b/docker/edge/http/stop.sh @@ -1,5 +1,10 @@ -docker stop metricbeat && docker rm $_ -docker stop edgebroker && docker rm $_ -docker stop edgeworker && docker rm $_ +sh $(pwd)/delete.sh +if [ $? -eq 0 ]; then + docker stop metricbeat && docker rm $_ + docker stop edgebroker && docker rm $_ + docker stop edgeworker && docker rm $_ +else + echo failed in delete Application +fi diff --git a/test/UnitTest/NGSI-LD/ld_data.py b/test/UnitTest/NGSI-LD/ld_data.py new file mode 100644 index 00000000..0342c212 --- /dev/null +++ b/test/UnitTest/NGSI-LD/ld_data.py @@ -0,0 +1,1241 @@ +# Payload to create entity without passing Link Header +subdata=\ +{ + "id": "urn:ngsi-ld:Vehicle:A020", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + +# Payload to create entity with context in link header +subdata1=\ +{ + "id": "urn:ngsi-ld:Vehicle:A100", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + + +# Payload to create entity with context inside payload +subdata2=\ + { + "@context": [{ + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "speed": "http://example.org/vehicle/speed", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + } + }], + "id": "urn:ngsi-ld:Vehicle:A4580", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + +# create entity with context in Link header and request payload is already expanded +subdata3=\ + { + "https://example.org/vehicle/brandName": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/Property" + ], + "https://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": "Mercedes" + } + ] + } + ], + "https://uri.etsi.org/ngsi-ld/createdAt": [ + { + "@type": "https://uri.etsi.org/ngsi-ld/DateTime", + "@value": "2017-07-29T12:00:04" + } + ], + "@id": "urn:ngsi-ld:Vehicle:A8866", + "https://example.org/common/isParked": [ + { + "https://uri.etsi.org/ngsi-ld/hasObject": [ + { + "@id": "urn:ngsi-ld:OffStreetParking:Downtown1" + } + ], + "https://uri.etsi.org/ngsi-ld/observedAt": [ + { + "@type": "https://uri.etsi.org/ngsi-ld/DateTime", + "@value": "2017-07-29T12:00:04" + } + ], + "https://example.org/common/providedBy": [ + { + "https://uri.etsi.org/ngsi-ld/hasObject": [ + { + "@id": "urn:ngsi-ld:Person:Bob" + } + ], + "@type": [ + "https://uri.etsi.org/ngsi-ld/Relationship" + ] + } + ], + "@type": [ + "https://uri.etsi.org/ngsi-ld/Relationship" + ] + } + ], + "https://uri.etsi.org/ngsi-ld/location": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/GeoProperty" + ], + "https://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": "{ \"type\":\"Point\", \"coordinates\":[ -8.5, 41.2 ] }" + } + ] + } + ], + "https://example.org/vehicle/speed": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/Property" + ], + "https://uri.etsi.org/ngsi-ld/hasValue": [ + { + "@value": 80 + } + ] + } + ], + "@type": [ + "https://example.org/vehicle/Vehicle" + ] + + } + + +# Payload to append additional attributes to an existing entity +subdata4=\ +{ + "@context": { + "brandName1": "http://example.org/vehicle/brandName1" + }, + "brandName1": { + "type": "Property", + "value": "BMW" + } + } + +# Payload to patch update specific attributes of an existing entity A100 +subdata5=\ +{ + "@context": { + "brandName1": "http://example.org/vehicle/brandName1" + }, + "brandName1": { + "type": "Property", + "value": "AUDI" + } + } + +# Payload to update the value of a specific attribute of an existing entity with wrong payload +subdata6=\ +{ + "@context": { + "brandName": "http://example.org/vehicle/brandName" + }, + "value": "MARUTI" + } + +# Payload to create a new context source registration, with context in link header +subdata7=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3459", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A456", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "id": "downtown", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15" + } + +# Payload to create a new context source registration, with context in request payload +subdata8=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a4000", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A555", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "id": "town$", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", + "@context": [ + + "https://forge.etsi.org/gitlab/NGSI-LD/NGSI-LD/raw/master/coreContext/ngsi-ld-core-context.jsonld", + { + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "brandName1": "http://example.org/vehicle/brandName1", + "speed": "http://example.org/vehicle/speed", + "totalSpotNumber": "http://example.org/parking/totalSpotNumber", + "reliability": "http://example.org/common/reliability", + "OffStreetParking": "http://example.org/parking/OffStreetParking", + "availableSpotNumber": "http://example.org/parking/availableSpotNumber", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "http://example.org/common/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + }, + "name": "http://example.org/common/name" + } + ] + } + +# Payload to update an existing context source registration, with context in request payload +subdata9=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3459", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A456", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed", + "brandName1" + ], + "relationships": [ + "isParked" + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", + "@context": [ + + "https://forge.etsi.org/gitlab/NGSI-LD/NGSI-LD/raw/master/coreContext/ngsi-ld-core-context.jsonld", + { + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "brandName1": "http://example.org/vehicle/brandName1", + "speed": "http://example.org/vehicle/speed", + "totalSpotNumber": "http://example.org/parking/totalSpotNumber", + "reliability": "http://example.org/common/reliability", + "OffStreetParking": "http://example.org/parking/OffStreetParking", + "availableSpotNumber": "http://example.org/parking/availableSpotNumber", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "http://example.org/common/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + }, + "name": "http://example.org/common/name", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "expires":"http://uri.etsi.org/ngsi-ld/expires" + } + ] + } + +# Payload to create a new Subscription to with context in Link header +subdata10=\ +{ + "id": "urn:ngsi-ld:Subscription:7", + "type": "Subscription", + "entities": [{ + "idPattern": ".*", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName"], + "notification": { + "attributes": ["brandName"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } + +# Payload to create entity which is to be tested for delete attribute request +subdata11=\ +{ + "id": "urn:ngsi-ld:Vehicle:A500", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + +# Payload to Update the subscription +subdata12=\ +{ + "id": "urn:ngsi-ld:Subscription:7", + "type": "Subscription", + "entities": [{ + "type": "Vehicle" + }], + "watchedAttributes": ["http://example.org/vehicle/brandName2"], + "q":"http://example.org/vehicle/brandName2!=Mercedes", + "notification": { + "attributes": ["http://example.org/vehicle/brandName2"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } + +# Payload to create entity without passing Header +subdata13=\ +{ + "id": "urn:ngsi-ld:Vehicle:A600", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + +# Payload to update entity with different header and posting duplicate attribute +subdata14=\ +{ + "@context": { + "brandName1": "http://example.org/vehicle/brandName1" + }, + "brandName1": { + "type": "Property", + "value": "MARUTI" + } + } + +# Payload to Update entity with different headers and passing inappropriate payload +subdata15=\ +{ + "@context": { + "brandName1": "http://example.org/vehicle/brandName1" + }, + "brandName1": { + + } + } + + +# Payload to create entity without attribute +subdata16=\ +{ + "id": "urn:ngsi-ld:Vehicle:A700", + "type": "Vehicle", + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + +# Payload to create entity without any attributes +subdata17=\ +{ + "id": "urn:ngsi-ld:Vehicle:A900", + "type": "Vehicle", + "createdAt": "2017-07-29T12:00:04" + } + +# Payload to create entity without any attribute to be tested for delete attribute request +subdata18=\ +{ + "id": "urn:ngsi-ld:Vehicle:A501", + "type": "Vehicle", + "createdAt": "2017-07-29T12:00:04" + } + + +# Payload to update an existing context source registration which contains idPattern , with context in request payload regarding one entity +subdata19=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a3459", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A456", + "type": "Vehicle" + } + ], + + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "idPattern": "downtown$", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", +"@context": [ + + "https://forge.etsi.org/gitlab/NGSI-LD/NGSI-LD/raw/master/coreContext/ngsi-ld-core-context.jsonld", + { + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "brandName1": "http://example.org/vehicle/brandName1", + "speed": "http://example.org/vehicle/speed", + "totalSpotNumber": "http://example.org/parking/totalSpotNumber", + "reliability": "http://example.org/common/reliability", + "OffStreetParking": "http://example.org/parking/OffStreetParking", + "availableSpotNumber": "http://example.org/parking/availableSpotNumber", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "http://example.org/common/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + }, + "name": "http://example.org/common/name", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "expires":"http://uri.etsi.org/ngsi-ld/expires" +} +] +} + +# Payload to update a specific subscription based on subscription id, with context in Link header and different payload +subdata20=\ +{ + "id": "urn:ngsi-ld:Subscription:7", + "type": "Subscription", + "entities": [{ + "type": "Vehicle" + }], + + "notification": { + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } + +# Payload to create registration with change in only 2nd entity in payload +subdata21=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a4001", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A456", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "id": "uptown$", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://127.0.0.1:8888//csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15" + } + +# Payload to create registration with only 1 entity in payload +subdata22=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a4001", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A666", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15" + } + +# Payload to update the corresponding csource registration using patch method +subdata23=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a4001", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A666", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed", + "brandName1" + ], + "relationships": [ + "isParked" + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15", +"@context": [ + + "https://forge.etsi.org/gitlab/NGSI-LD/NGSI-LD/raw/master/coreContext/ngsi-ld-core-context.jsonld", + { + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "brandName1": "http://example.org/vehicle/brandName1", + "speed": "http://example.org/vehicle/speed", + "totalSpotNumber": "http://example.org/parking/totalSpotNumber", + "reliability": "http://example.org/common/reliability", + "OffStreetParking": "http://example.org/parking/OffStreetParking", + "availableSpotNumber": "http://example.org/parking/availableSpotNumber", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "isNextToBuilding": { + "@type": "@id", + "@id": "http://example.org/common/isNextToBuilding" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + }, + "name": "http://example.org/common/name", + "timestamp": "http://uri.etsi.org/ngsi-ld/timestamp", + "expires":"http://uri.etsi.org/ngsi-ld/expires" +} +] +} + +#Payload to create csource registration with missing attributes +subdata24=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a4002", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A662", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15" + } + +# Inappropriate payload to perform patch update +subdata25=\ +{ + "type":"ContextSourceRegistration" +} + +# Empty Payload +subdata26=\ +{ +} + +# Payload to create entity to check for CreatedAt and ModifiedAt +subdata27=\ +{ + "id": "urn:ngsi-ld:Vehicle:A6000", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + +# Payload to create csource registration with idPattern +subdata28=\ +{ + "id": "urn:ngsi-ld:ContextSourceRegistration:csr1a7000", + "type": "ContextSourceRegistration", + "name": "NameExample", + "description": "DescriptionExample", + "information": [ + { + "entities": [ + { + "id": "urn:ngsi-ld:Vehicle:A.*", + "type": "Vehicle" + } + ], + "properties": [ + "brandName", + "speed" + ], + "relationships": [ + "isParked" + ] + }, + { + "entities": [ + { + "idPattern": "pqr$", + "type": "OffStreetParking" + } + ] + } + ], + "endpoint": "http://127.0.0.1:8888/csource", + "location": "{ \"type\": \"Point\", \"coordinates\": [-8.5, 41.2] }", + "timestamp": { + "start": "2017-11-29T14:53:15" + }, + "expires": "2030-11-29T14:53:15" + } + +# Payload to create Subscription to check for Modified At and Created At in susbcription +subdata29=\ +{ + "id": "urn:ngsi-ld:Subscription:8", + "type": "Subscription", + "entities": [{ + "idPattern": ".*", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName"], + "notification": { + "attributes": ["brandName"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } + +# Payload to create a Subscription with id as urn:ngsi-ld:Subscription:10 +subdata30=\ +{ + "id": "urn:ngsi-ld:Subscription:10", + "type": "Subscription", + "entities": [{ + "idPattern": ".*", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName"], + "notification": { + "attributes": ["brandName"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } + +# Payload to update the corresponding subscription +subdata31=\ +{ + "id": "urn:ngsi-ld:Subscription:10", + "type": "Subscription", + "entities": [{ + "type": "Vehicle" + }], + "watchedAttributes": ["http://example.org/vehicle/brandName2"], + "q":"http://example.org/vehicle/brandName2!=Mercedes", + "notification": { + "attributes": ["http://example.org/vehicle/brandName2"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } +# Payload to create an entity which is to be checked for delete request +subdata32=\ +{ + "id": "urn:ngsi-ld:Vehicle:A999", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } + } + +# Payload for entity creation with nested property with context in payload +subdata33=\ +{ + "id": "urn:ngsi-ld:Vehicle:B990", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "providedBy": { + "type": "person", + "object": "urn:ngsi-ld:Person:Bob" + }, + "parkingDate": { + "type": "Property", + "value": "2017-07-29T12:00:04" + }, + "availableSpotNumber": { + "type": "Property", + "value": "121", + "reliability": { + "type": "Property", + "value": "0.7" + }, + "providedBy": { + "type": "relationship", + "object": "urn:ngsi-ld:camera:c1" + } + } + }, + "@context": [ + { + "Vehicle": "http://example.org/vehicle/Vehicle", + "brandName": "http://example.org/vehicle/brandName", + "speed": "http://example.org/vehicle/speed", + "isParked": { + "@type": "@id", + "@id": "http://example.org/common/isParked" + }, + "providedBy": { + "@type": "@id", + "@id": "http://example.org/common/providedBy" + } + } + ] +} + +# Payload to create nested entity with context in link header +subdata34=\ +{ + "id": "urn:ngsi-ld:Vehicle:C001", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "providedBy": { + "type": "person", + "object": "urn:ngsi-ld:Person:Bob" + }, + "parkingDate": { + "type": "Property", + "value": "2017-07-29T12:00:04" + }, + "availableSpotNumber": { + "type": "Property", + "value": "121", + "reliability": { + "type": "Property", + "value": "0.7" + }, + "providedBy": { + "type": "relationship", + "object": "urn:ngsi-ld:camera:c1" + } + } + } +} + + +#Payload to create new entity with different context link +subdata35=\ +{ + "id": "urn:ngsi-ld:Vehicle:A909", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } +} + +subdata36=\ +{ + "id": "urn:ngsi-ld:Subscription:19", + "type": "Subscription", + "entities": [{ + "idPattern": ".*", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName","speed"], + "notification": { + "attributes": ["brandName","speed"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } + + +subdata37=\ +{ + "id": "urn:ngsi-ld:Subscription:20", + "type": "Subscription", + "entities": [{ + "id": "urn:ngsi-ld:Vehicle:A3000", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName","speed"], + "notification": { + "attributes": ["brandName","speed"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + }, + "context": ["https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"] + } + + +subdata38=\ +{ + "id": "urn:ngsi-ld:Vehicle:A3000", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } +} + +subdata39=\ +{ + "id": "urn:ngsi-ld:Subscription:020", + "type": "Subscription", + "entities": [{ + "idPattern": ".*", + "type": "Vehicle" + }], + "watchedAttributes": ["brandName","speed"], + "notification": { + "attributes": ["brandName","speed"], + "format": "keyValues", + "endpoint": { + "uri": "http://127.0.0.1:8888/ld-notify", + "accept": "application/json" + } + } + } + +subdata40=\ +{ + "id": "urn:ngsi-ld:Vehicle:A4000", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "speed": { + "type": "Property", + "value": 80 + }, + "createdAt": "2017-07-29T12:00:04", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [-8.5, 41.2] + } + } +} + +subdata41=\ +{ + "@context": { + "brandName": "brandName" + }, + "value": "BMW1" +} + diff --git a/test/UnitTest/NGSI-LD/test_casesNGSI-LD.py b/test/UnitTest/NGSI-LD/test_casesNGSI-LD.py new file mode 100644 index 00000000..db910f56 --- /dev/null +++ b/test/UnitTest/NGSI-LD/test_casesNGSI-LD.py @@ -0,0 +1,1326 @@ +import os,sys +# change the path accoring to the test folder in system +from datetime import datetime +import copy +import json +import requests +import time +import pytest +import ld_data +import sys + +# change it by broker ip and port +brokerIp="http://localhost:8070" +discoveryIp="http://localhost:8090" + +print("Testing of NGSI-LD") +# testCase 1 +''' + To test create entity with context in Link Header +''' +def test_case1(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata1),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 201 + +#testCase 2 +''' + To test create entity with context in payload +''' +def test_case2(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata2),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 201 + +#testCase 3 +''' + To test create entity with context in Link header and request payload is already expanded +''' +def test_case3(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata3),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 201 + +#testCase 4 +''' + To test to append additional attributes to an existing entity +''' +def test_case4(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(ld_data.subdata4),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 204 + + +#testCase 5 +''' + To test to patch update specific attributes of an existing entity A100 +''' +def test_case5(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs" + headers={'Content-Type' : 'application/json'} + r=requests.patch(url,data=json.dumps(ld_data.subdata5),headers=headers) + print(r.status_code) + assert r.status_code == 204 + +#testCase 6 +''' + To test to update the value of a specific attribute of an existing entity with wrong payload +''' +def test_case6(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4580/attrs/brandName" + headers={'Content-Type' : 'application/json'} + r=requests.patch(url,data=json.dumps(ld_data.subdata6),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 204 + +#testCase 7 +''' + To test create entity without passing Header +''' +def test_case7(): + url=brokerIp+"/ngsi-ld/v1/entities/" + r=requests.post(url,data=json.dumps(ld_data.subdata13)) + print(r.content) + print(r.status_code) + assert r.status_code == 400 + +#testCase 8 +''' + To test create entity without passing Link Header +''' +def test_case8(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 201 + +#testCase 9 +''' + To test Update entity with two header namely Content Type and Accept header and posting duplicate attribute +''' +def test_case9(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata14),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 207 + +#testCase 10 +''' + To test Update entity with wrong id +''' +def test_case10(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A800/attrs" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata14),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 404 + +#testCase 11 +''' + To test Update entity with wrong id and not passing Accept Header +''' +def test_case11(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A800/attrs" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(ld_data.subdata14),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 404 + +#testCase 12 +''' + To test Update entity with three header namely Content-Type, Accept and context Link +''' +def test_case12(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4580/attrs" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(ld_data.subdata14),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 204 + +#testCase 13 +''' + To test Update entity with different headers namely Content-Type, Accept and context Link and passing inappropriate payload +''' +def test_case13(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata15),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 500 + +#testCase 14 +''' + To test Update entity without header +''' +def test_case14(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs" + r=requests.post(url,data=json.dumps(ld_data.subdata14)) + print(r.content) + print(r.status_code) + assert r.status_code == 400 + +#testCase 15 +''' + To test Update entity patch request with different header namely Content-Type, Accept and context Link and passing inappropriate payload +''' +def test_case15(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4580/attrs" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.patch(url,data=json.dumps(ld_data.subdata15),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 500 + +#testCase 16 +''' + To test to update entity by first creating entity without corresponding attribute +''' +def test_case16(): + #create NGSI-LD entity + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata16),headers=headers) + print(r.content) + print(r.status_code) + + #update the corresponding entity using patch + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A700/attrs" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.patch(url,data=json.dumps(ld_data.subdata14),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 207 + +#testCase 17 +''' + To test Update entity patch request without header +''' +def test_case17(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A100/attrs" + r=requests.patch(url,data=json.dumps(ld_data.subdata14)) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 18 +''' + To test to update entity by first creating entity with all attributes missing +''' +def test_case18(): + #create NGSI-LD entity + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata17),headers=headers) + #print(r.content) + #print(r.status_code) + + #update the corresponding entity using patch + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A900/attrs" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.patch(url,data=json.dumps(ld_data.subdata14),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 207 + +#testCase 19 +''' + To test to delete NGSI-LD context entity +''' +def test_19(): + #create NGSI-LD entity + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata32),headers=headers) + #print(r.content) + #print(r.status_code) + + #to delete corresponding entity + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A999" + headers={'Content-Type':'application/json','Accept':'application/ld+json'} + r=requests.delete(url,headers=headers) + #print(r.status_code) + assert r.status_code == 204 + +#testCase 20 +''' + To test to delete an attribute of an NGSI-LD context entity +''' +def test_case20(): + #create NGSI-LD entity + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata11),headers=headers) + #print(r.content) + #print(r.status_code) + + #to append the attribute of corresponding entity + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A500/attrs" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(ld_data.subdata4),headers=headers) + #print(r.content) + #print(r.status_code) + + #to delete the attribute of corresponding entity + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A500/attrs/brandName1" + r=requests.delete(url) + #print(r.status_code) + assert r.status_code == 204 + +#testCase 21 +''' + To test to delete an attribute of an NGSI-LD context entity which does not have any attribute +''' +def test_case21(): + #create NGSI-LD entity + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata18),headers=headers) + #print(r.content) + #print(r.status_code) + + #to delete attribute of corresponding entity + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A501/attrs/brandName1" + r=requests.delete(url) + print(r.content) + #print(r.status_code) + assert r.status_code == 404 + + +#testCase 22 +''' + To test to retrieve a specific entity which is deleted +''' +def test_case22(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A999" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 404 + +#testCase 23 +''' + To test to retrieve a specific entity which is existing +''' +def test_case23(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4580" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["id"]=="urn:ngsi-ld:Vehicle:A4580": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 24 +''' + To test to retrieve entities by attributes +''' +def test_case24(): + url=brokerIp+"/ngsi-ld/v1/entities?attrs=http://example.org/vehicle/brandName" + headers={'Content-Type':'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["http://example.org/vehicle/brandName"]["value"]=="MARUTI": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 25 +''' + To test to retrieve entities by attributes with wrong query +''' +def test_case25(): + url=brokerIp+"/ngsi-ld/v1/entities?attrs" + headers={'Content-Type':'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 26 +''' + To test to retrieve a specific entity by ID and Type +''' +def test_case26(): + url=brokerIp+"/ngsi-ld/v1/entities?id=urn:ngsi-ld:Vehicle:A4580&type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["type"]=="http://example.org/vehicle/Vehicle" and resp[0]["id"]=="urn:ngsi-ld:Vehicle:A4580": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 27 +''' + To test to retrieve a specific entity by Type +''' +def test_case27(): + url=brokerIp+"/ngsi-ld/v1/entities?type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["type"]=="http://example.org/vehicle/Vehicle": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 28 +''' + To test to retrieve a specific entity by Type with wrong query +''' +def test_case28(): + url=brokerIp+"/ngsi-ld/v1/entities?type=http://example.org" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 404 + +#testCase 29 +''' + To test to retrieve entities by Type, with context in Link Header +''' +def test_case29(): + url=brokerIp+"/ngsi-ld/v1/entities?type=Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["type"]=="Vehicle": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 30 +''' + To test to retrieve a specific entity by Type, context in Link Header and wrong query +''' +def test_case30(): + url=brokerIp+"/ngsi-ld/v1/entities?type" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 31 +''' + To test to To retrieve a specific entity by IdPattern and Type +''' +def test_case31(): + url=brokerIp+"/ngsi-ld/v1/entities?idPattern=urn:ngsi-ld:Vehicle:A.*&type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["type"]=="http://example.org/vehicle/Vehicle" and resp[0]["id"].find("A")!=-1: + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 32 +''' + To test to retrieve an entity registered over Discovery +''' +def test_case32(): + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A4580" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["ID"]=="urn:ngsi-ld:Vehicle:A4580": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 33 +''' + To test to create a new context source registration, with context in link header +''' +def test_case33(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata7),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 201 + +#testCase 34 +''' + To test to create a new context source registration, with context in request payload +''' +def test_case34(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata8),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 201 + +#testCase 35 +''' + To test to create a new context source registration, without header +''' +def test_case35(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + r=requests.post(url,data=json.dumps(ld_data.subdata7)) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 36 +''' + To test to create registration with change in only 2nd entity in payload +''' +def test_case36(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata21),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 409 + +#testCase 37 +''' + To test to create registration with only 1 entity in payload +''' +def test_case37(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata22),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 201 + +#testcase 38 +''' + To test get the regiestered source entity on discovery for id = town$ +''' +def test_case38(): + url=discoveryIp+"/ngsi9/registration/town$" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["ID"]=="town$": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase39 +''' + To test Update registration on discovery if it is reflecing or not +''' +def test_case39(): + #get before update + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A666" + r=requests.get(url) + #print(r.content) + #print(r.status_code) + + #patch request to update + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a4001" + headers={'Content-Type' : 'application/json'} + r=requests.patch(url,data=json.dumps(ld_data.subdata23),headers=headers) + #print(r.content) + #print(r.status_code) + + #fetching from discovery + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A666" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["ID"]=="urn:ngsi-ld:Vehicle:A666": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 40 +''' + To test update request for registration with wrong payload +''' +def test_case40(): + #create registration + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata24),headers=headers) + #print(r.content) + #print(r.status_code) + + + #patch request to update + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a4002" + headers={'Content-Type' : 'application/json'} + r=requests.patch(url,data=json.dumps(ld_data.subdata25),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 204 + +#testCase 41 +''' + To test the get entity from discovery for enitity Id = A662 +''' +def test_case41(): + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A662" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["ID"]=="urn:ngsi-ld:Vehicle:A662": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + + +#testCase 42 +''' + To test to update an existing context source registration, with context in request payload +''' +def test_case42(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a4002" + headers={'Content-Type' : 'application/json'} + r=requests.patch(url,data=json.dumps(ld_data.subdata9),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 204 + + +#testCase 43 +''' + To test to update an existing context source registration with idPattern , with context in request payload regarding one entity +''' +def test_case43(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a3459" + headers={'Content-Type' : 'application/json'} + r=requests.patch(url,data=json.dumps(ld_data.subdata19),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 44 +''' + To test to get a registration by Type +''' +def test_case44(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["information"][0]["entities"][0]["type"]=="http://example.org/vehicle/Vehicle": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + + +#testCase 45 +''' + To test to get a registration by Type, context in Link Header +''' +def test_case45(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?type=Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["information"][0]["entities"][0]["type"]=="https://uri.etsi.org/ngsi-ld/default-context/Vehicle": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 46 +''' + To test to get a registration by ID and Type +''' +def test_case46(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?id=urn:ngsi-ld:Vehicle:A456&type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["information"][0]["entities"][0]["type"]=="http://example.org/vehicle/Vehicle" and resp[0]["information"][0]["entities"][0]["id"]=="urn:ngsi-ld:Vehicle:A456": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 47 +''' + To test to get a registration by IdPattern and Type +''' +def test_case47(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?idPattern=urn:ngsi-ld:Vehicle:A*&type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + assert r.status_code == 404 + +#testCase 48 +''' + To test to delete an existing context source registration based on registration id +''' +def test_case48(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/urn:ngsi-ld:ContextSourceRegistration:csr1a3459" + r=requests.delete(url) + #print(r.status_code) + assert r.status_code == 204 + + +#testCase 49 +''' + To test to get registration by Type with wrong query +''' +def test_case49(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?type=" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 50 +''' + To test to get registration by idPattern and Type with wrong query +''' +def test_case50(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?idPattern=&type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 51 +''' + To test to get a registration by ID and Type with werong query +''' +def test_case51(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?id&type=http://example.org/vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 52 +''' + To test to create a new Subscription to with context in Link header +''' +def test_case52(): + #create subscription + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata10),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 201 + +#testCase 53 +''' + To test to retrieve all the subscriptions +''' +def test_case53(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Accept' : 'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 200 + +#testCase 54 +''' + To test to retrieve a specific subscription based on subscription id +''' +def test_case54(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:7" + headers={'Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["id"]=="urn:ngsi-ld:Subscription:7": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 55 +''' + To test to update a specific subscription based on subscription id, with context in Link header +''' +def test_case55(): + #get subscription before update + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:7" + headers={'Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + #print(r.status_code) + + #Update the subscription + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:7" + headers={'Content-Type':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.patch(url,data=json.dumps(ld_data.subdata12),headers=headers) + #print(r.content) + #print(r.status_code) + + #get subscription after update + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:7" + headers={'Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["id"]=="urn:ngsi-ld:Subscription:7": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 56 +''' + To test to update a specific subscription based on subscription id, without header +''' +def test_case56(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:7" + r=requests.patch(url,data=json.dumps(ld_data.subdata12)) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 57 +''' + To test to update a specific subscription based on subscription id, with context in Link header and different payload +''' +def test_case57(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:7" + headers={'Content-Type':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.patch(url,data=json.dumps(ld_data.subdata20),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 204 + +#testCase 58 +''' + To test to delete a specific subscription based on subscription id +''' +def test_case58(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:7" + r=requests.delete(url) + #print(r.status_code) + assert r.status_code == 204 + +#testCase 59 +''' + To test for empty payload in entity creation +''' +def test_case59(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata26),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 60 +''' + To test for empty payload in csource registration +''' +def test_case60(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata26),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 61 +''' + To test for empty payload in subscription +''' +def test_case61(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata26),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 62 +''' + To test for ModifiedAt and CreatedAt in entity creation +''' +def test_case62(): + #Entity Creation + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata27),headers=headers) + #print(r.content) + #print(r.status_code) + + #Fetching Entity + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A6000" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["id"]=="urn:ngsi-ld:Vehicle:A6000": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 63 +''' + To test for ModifiedAt and CreatedAt in Csource Registration +''' +def test_case63(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations?type=http://example.org/vehicle/Vehicle" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp[0]["information"][0]["entities"][0]["type"]=="http://example.org/vehicle/Vehicle": + print("\nValidated") + else: + print("\nNot Validated") + print(r.status_code) + assert r.status_code == 200 + +#testCase 64 +''' + To test for ModifiedAt and CreatedAt in susbcription +''' +def test_case64(): + #create subscription + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata29),headers=headers) + #print(r.content) + #print(r.status_code) + + #making a get request + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:8" + headers={'Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["id"]=="urn:ngsi-ld:Subscription:8": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 65 +''' + To test for csource registartion with Id pattern +''' +def test_case65(): + url=brokerIp+"/ngsi-ld/v1/csourceRegistrations/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata28),headers=headers) + print(r.content) + #print(r.status_code) + assert r.status_code == 400 + +#testCase 66 +''' + To test for update subscription over discovery +''' +def test_case66(): + #create a subscription + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata30),headers=headers) + #print(r.content) + #print(r.status_code) + + #get subscription over discovery before update + url=discoveryIp+"/ngsi9/subscription" + r=requests.get(url) + #print(r.content) + #print(r.status_code) + + #update the subscription + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:10" + headers={'Content-Type':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.patch(url,data=json.dumps(ld_data.subdata31),headers=headers) + #print(r.content) + #print(r.status_code) + + #get subscription after update + url=discoveryIp+"/ngsi9/subscription" + r=requests.get(url) + #print(r.content) + #print(r.status_code) + assert r.status_code == 200 + +#testCase 67 +''' + To test entity creation with nested property with context in payload +''' +def test_case67(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata33),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 201 + +#testCase 68 +''' + To test create entity with nested property with context in Link +''' +def test_case68(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata34),headers=headers) + print(r.content) + print(r.status_code) + assert r.status_code == 201 + +#testCase 69 +''' + To test to retrieve entity with id as urn:ngsi-ld:B990 +''' +def test_case69(): + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:B990" + headers={'Content-Type' : 'application/ld+json','Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + #print(r.content) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["id"]=="urn:ngsi-ld:Vehicle:B990": + print("\nValidated") + else: + print("\nNot Validated") + print(r.status_code) + assert r.status_code == 200 + +#testCase 70 +''' + To test and retrieve the entity from discovery +''' +def test_case70(): + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:C001" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["ID"]=="urn:ngsi-ld:Vehicle:C001": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + + +#testCase 71 +''' + To test entity creation with different Context Link in Header +''' +def test_case71(): + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept' : 'application/ld+json','Link' : '<{{link}}>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata35),headers=headers) + #print(r.content) + print(r.status_code) + assert r.status_code == 201 + + +#testCase 72 +''' + To test if multiple subscription can be created with same subscription Id +''' +def test_case72(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata36),headers=headers) + #print(r.content) + #print(r.status_code) + + #get subscription over discovery before update + url=discoveryIp+"/ngsi9/subscription" + r=requests.get(url) + #print(r.content) + #print(r.status_code) + + #to create same subscription again + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata36),headers=headers) + print(r.content) + #print(r.status_code) + assert r.status_code == 409 + +#testCase 73 +''' + To test if subscription can be created with context in Payload +''' +def test_case73(): + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json'} + r=requests.post(url,data=json.dumps(ld_data.subdata37),headers=headers) + #print(r.content) + #print(r.status_code) + + #to get the subscription from broker + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:20" + headers={'Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + if resp["id"]=="urn:ngsi-ld:Subscription:20": + print("\nValidated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 + +#testCase 74 +''' + To test if delete attribute is reflected over discovery +''' +def test_case74(): + #to create an entity + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata38),headers=headers) + #print(r.content) + #print(r.status_code) + + #to fetch the registration from discovery + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A3000" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp["AttributesList"]["https://uri.etsi.org/ngsi-ld/default-context/brandName"]) + print("\nchecking if brandName attribute is present in discovery before deletion") + if resp["ID"]=="urn:ngsi-ld:Vehicle:A3000": + if resp["AttributesList"]["https://uri.etsi.org/ngsi-ld/default-context/brandName"]["type"] == "Property": + print("\n-----> brandName is existing...!!") + else: + print("\n-----> brandName does not exist..!") + else: + print("\nNot Validated") + #print(r.status_code) + + + #to delete brandName attribute + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A3000/attrs/brandName" + r=requests.delete(url) + #print(r.content) + #print(r.status_code) + + #To fetch registration again from discovery + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A3000" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp["AttributesList"]) + print("\nchecking if brandName attribute is present in discovery after deletion") + if resp["ID"]=="urn:ngsi-ld:Vehicle:A3000": + if "https://uri.etsi.org/ngsi-ld/default-context/brandName" in resp["AttributesList"]: + print("\n-----> brandName is existing...!!") + else: + print("\n-----> brandName does not exist because deleted...!") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 75 +''' + To test if appended attribute is reflected on discovery +''' +def test_case75(): + #to fetch registration of entity from discovery before appending + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A3000" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp["AttributesList"]) + print("\nchecking if brandName1 attribute is present in discovery after deletion") + if resp["ID"]=="urn:ngsi-ld:Vehicle:A3000": + if "http://example.org/vehicle/brandName1" in resp["AttributesList"]: + print("\n-----> brandName1 is existing...!!") + else: + print("\n-----> brandName1 does not exist yet...!") + else: + print("\nNot Validated") + + #to append an entity with id as urn:ngsi-ld:Vehicle:A3000 + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A3000/attrs" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(ld_data.subdata4),headers=headers) + #print(r.content) + #print(r.status_code) + assert r.status_code == 204 + + #to fetch registration of entity from discovery after appending + url=discoveryIp+"/ngsi9/registration/urn:ngsi-ld:Vehicle:A3000" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp["AttributesList"]) + print("\nchecking if brandName1 attribute is present in discovery after deletion") + if resp["ID"]=="urn:ngsi-ld:Vehicle:A3000": + if "http://example.org/vehicle/brandName1" in resp["AttributesList"]: + print("\n-----> brandName1 is existing after appending...!!") + else: + print("\n-----> brandName1 does not exist yet...!") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 76 +''' + To test if Subscription contains context when context is sent in payload + +def test_case76(): + #to fetch a subscription containing context + url=brokerIp+"/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:20" + headers={'Accept':'application/ld+json'} + r=requests.get(url,headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp['context'][0]) + if resp["id"]=="urn:ngsi-ld:Subscription:20": + if resp['context'][0] == 'https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld': + print("\nValidated") + else: + print("\nNot Validated") + else: + print("\nNot Validated") + #print(r.status_code) + assert r.status_code == 200 +''' + +#testCase 76 +''' + To test if discovery's context availablity is updated on updating +''' +def test_case76(): + #to create entity + url=brokerIp+"/ngsi-ld/v1/entities/" + headers={'Content-Type' : 'application/json','Accept':'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata40),headers=headers) + #print(r.content) + #print(r.status_code) + + #to create subscription + url=brokerIp+"/ngsi-ld/v1/subscriptions/" + headers={'Content-Type' : 'application/ld+json','Link':'<{{link}}>; rel="https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld"; type="application/ld+json"'} + r=requests.post(url,data=json.dumps(ld_data.subdata39),headers=headers) + #print(r.content) + #print(r.status_code) + + #Update entity to fire notification + url=brokerIp+"/ngsi-ld/v1/entities/urn:ngsi-ld:Vehicle:A4000/attrs" + headers={'Content-Type' : 'application/json'} + r=requests.patch(url,data=json.dumps(ld_data.subdata41),headers=headers) + #print(r.status_code) + + #to validate + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : "urn:ngsi-ld:Subscription:020"}) + print(r.content) + assert r.status_code == 200 + diff --git a/test/UnitTest/accumulator.py b/test/UnitTest/accumulator.py new file mode 100644 index 00000000..13a5fcff --- /dev/null +++ b/test/UnitTest/accumulator.py @@ -0,0 +1,86 @@ + +from flask import Flask, abort, request +import requests +import json +app = Flask(__name__) + +myStatus = 'off' + +subId = [] + +# Getting notification for Quantumleap and sending response 200 to the +# test module + + +@app.route('/accumulate', methods=['POST']) +def getUpdateNotification(): + print(dir(request)) + data = request.get_json() + print(data) + pload = data["subscriptionId"] + subId.append(data["subscriptionId"]) + print(pload) + return "Done" + + +@app.route('/csource', methods=['POST']) +def getNotifiedLD_csource(): + data = request.get_json() + print(data) + return "Done" + + +@app.route('/ld-notify', methods=['POST']) +def getNotifiedLD_subscription(): + data = request.get_json() + print(data) + pload = data["subscriptionId"] + subId.append(data["subscriptionId"]) + print(pload) + print(subId) + return "Done" + + +@app.route('/v2/notifyContext', methods=['POST']) +def getUpdateNotificatio1n(): + # dir(request) + data = request.get_json() + print(data) + print(data["subscriptionId"]) + subId.append(data["subscriptionId"]) + print(subId) + return "Done." + + +@app.route('/validateNotification', methods=['POST']) +def getValidationNotification(): + data = request.get_json(force=True) + print(data) + pload = data["subscriptionId"] + print(pload) + print(subId) + if pload in subId: + print("validated the method") + return "Validated" + else: + print("Not validated") + return "Not validated" + +@app.route('/ngsi10/updateContext',methods=['POST']) +def getNotified_southbound(): + data = request.get_json() + print(data) + if data["contextElements"][0]["attributes"][0]["type"]=="command" and data["contextElements"][0]["attributes"][0]["name"]=="on": + print("validated the method") + else: + print("Not validated") + #pload = data["subscriptionId"] + #subId.append(data["subscriptionId"]) + #print(pload) + #print(subId) + return "Notification of command recieved" + + +# main file for starting application +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8888, debug=True) diff --git a/test/UnitTest/persistance/data.py b/test/UnitTest/persistance/data.py new file mode 100644 index 00000000..c9ade5d4 --- /dev/null +++ b/test/UnitTest/persistance/data.py @@ -0,0 +1,322 @@ +# Payload to persist Operator +test0 =\ + { + "contextElements": [{ + "entityId": { + "type": "Operator", + "id": "test011" + }, + "attributes": [{ + "name": "designboard", + "type": "object", + "value": {} + }, { + "name": "operator", + "type": "object", + "value": { + "description": "", + "name": "recommender", + "parameters": [] + } + }], + "domainMetadata": [{ + "name": "location", + "type": "global", + "value": "global" + }] + }], + "updateAction": "UPDATE" + } + +# Payload to persist FogFunction +test1 =\ + { + "contextElements": [{ + "entityId": { + "type": "FogFunction", + "id": "test2" + }, + "attributes": [{ + "name": "name", + "type": "string", + "value": "Test" + }, { + "name": "topology", + "type": "object", + "value": { + "description": "just for a simple test", + "name": "Test", + "tasks": [{ + "input_streams": [{ + "groupby": "EntityID", + "scoped": "false", + "selected_attributes": [], + "selected_type": "Temperature" + }], + "name": "Main", + "operator": "dummy", + "output_streams": [{ + "entity_type": "Out" + }] + }] + } + }, { + "name": "designboard", + "type": "object", + "value": { + "blocks": [{ + "id": 1, + "module": "null", + "type": "Task", + "values": { + "name": "Main", + "operator": "dummy", + "outputs": ["Out"] + }, + "x": 123, + "y": -99 + }, { + "id": 2, + "module": "null", + "type": "EntityStream", + "values": { + "groupby": "EntityID", + "scoped": "false", + "selectedattributes": ["all"], + "selectedtype": "Temperature" + }, + "x": -194, + "y": -97 + }], + "edges": [{ + "block1": 2, + "block2": 1, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 1 + }] + } + }, { + "name": "intent", + "type": "object", + "value": { + "geoscope": { + "scopeType": "global", + "scopeValue": "global" + }, + "priority": { + "exclusive": "false", + "level": 0 + }, + "qos": "Max Throughput", + "topology": "Test" + } + }, { + "name": "status", + "type": "string", + "value": "enabled" + }], + "domainMetadata": [{ + "name": "location", + "type": "global", + "value": "global" + }] + } + + ], + "updateAction": "UPDATE" + } + +# Payload to persist DockerImage +test2 =\ + { + "contextElements": [{ + "entityId": { + "type": "DockerImage", + "id": "test3" + }, + "attributes": [{ + "name": "image", + "type": "string", + "value": "fogflow/counter" + }, { + "name": "tag", + "type": "string", + "value": "latest" + }, { + "name": "hwType", + "type": "string", + "value": "X86" + }, { + "name": "osType", + "type": "string", + "value": "Linux" + }, { + "name": "operator", + "type": "string", + "value": "counter" + }, { + "name": "prefetched", + "type": "boolean", + "value": "false" + }], + "domainMetadata": [{ + "name": "operator", + "type": "string", + "value": "counter" + }, { + "name": "location", + "type": "global", + "value": "global" + }] + }], + "updateAction": "UPDATE" + } + +# payload to persist Topology +test3 =\ + { + "contextElements": [{ + "entityId": { + "type": "Topology", + "id": "test4" + }, + "attributes": [{ + "name": "status", + "type": "string", + "value": "enabled" + }, { + "name": "designboard", + "type": "object", + "value": { + "blocks": [{ + "id": 1, + "module": "null", + "type": "Task", + "values": { + "name": "Counting", + "operator": "counter", + "outputs": ["Stat"] + }, + "x": 202, + "y": -146 + }, { + "id": 2, + "module": "null", + "type": "Task", + "values": { + "name": "Detector", + "operator": "anomaly", + "outputs": ["Anomaly"] + }, + "x": -194, + "y": -134 + }, { + "id": 3, + "module": "null", + "type": "Shuffle", + "values": { + "groupby": "ALL", + "selectedattributes": ["all"] + }, + "x": 4, + "y": -18 + }, { + "id": 4, + "module": "null", + "type": "EntityStream", + "values": { + "groupby": "EntityID", + "scoped": "true", + "selectedattributes": ["all"], + "selectedtype": "PowerPanel" + }, + "x": -447, + "y": -179 + }, { + "id": 5, + "module": "null", + "type": "EntityStream", + "values": { + "groupby": "ALL", + "scoped": "false", + "selectedattributes": ["all"], + "selectedtype": "Rule" + }, + "x": -438, + "y": -5 + }], + "edges": [{ + "block1": 3, + "block2": 1, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 2 + }, { + "block1": 2, + "block2": 3, + "connector1": ["outputs", "output", 0], + "connector2": ["in", "input"], + "id": 3 + }, { + "block1": 4, + "block2": 2, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 4 + }, { + "block1": 5, + "block2": 2, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 5 + }] + } + }, { + "name": "template", + "type": "object", + "value": { + "description": "detect anomaly events in shops", + "name": "anomaly-detection", + "tasks": [{ + "input_streams": [{ + "groupby": "ALL", + "scoped": "true", + "selected_attributes": [], + "selected_type": "Anomaly" + }], + "name": "Counting", + "operator": "counter", + "output_streams": [{ + "entity_type": "Stat" + }] + }, { + "input_streams": [{ + "groupby": "EntityID", + "scoped": "true", + "selected_attributes": [], + "selected_type": "PowerPanel" + }, { + "groupby": "ALL", + "scoped": "false", + "selected_attributes": [], + "selected_type": "Rule" + }], + "name": "Detector", + "operator": "anomaly", + "output_streams": [{ + "entity_type": "Anomaly" + }] + }] + } + }], + "domainMetadata": [{ + "name": "location", + "type": "global", + "value": "global" + }] + } + + ], + "updateAction": "UPDATE" + } diff --git a/test/UnitTest/persistance/test_persistance.py b/test/UnitTest/persistance/test_persistance.py new file mode 100644 index 00000000..32242caf --- /dev/null +++ b/test/UnitTest/persistance/test_persistance.py @@ -0,0 +1,186 @@ +import os +import copy +import json +import requests +import time +import pytest +import data +import sys + +# change it by broker ip and port +designerIp="http://localhost:8080" +brokerIp= "http://localhost:8070" + +''' + test registration for opearator +''' + + +def test_persistOPerator(): + brokerUrl = brokerIp + "/ngsi10/entity/test011" + designerUrl = designerIp + "/ngsi10/updateContext" + headers = {'Content-Type': 'application/json'} + r = requests.post( + designerUrl, + data=json.dumps( + data.test0), + headers=headers) + r = requests.get(brokerUrl, headers=headers) + # print(r.content) + resp_content = r.content + resInJson = resp_content.decode('utf8').replace("'", '"') + resp = json.loads(resInJson) + if resp["entityId"]["type"] == "Operator" and resp["entityId"]["id"] == "test011": + print "\nValidated" + else: + print "\nNot Validated" + assert r.status_code == 200 + + +''' + test registration for fogfunction +''' + + +def test_persistFogFunction(): + brokerUrl = brokerIp+ "/ngsi10/entity/test2" + designerUrl = designerIp + "/ngsi10/updateContext" + headers = {'Content-Type': 'application/json'} + r = requests.post( + designerUrl, + data=json.dumps( + data.test1), + headers=headers) + r = requests.get(brokerUrl, headers=headers) + # print(r.content) + resp_content = r.content + resInJson = resp_content.decode('utf8').replace("'", '"') + resp = json.loads(resInJson) + if resp["entityId"]["type"] == "FogFunction" and resp["entityId"]["id"] == "test2": + print "\nValidated" + else: + print "\nNot Validated" + assert r.status_code == 200 + + +''' + test registration for dockerImage +''' + + +def test_persistDockerImage(): + brokerUrl = brokerIp+ "/ngsi10/entity/test3" + designerUrl = designerIp + "/ngsi10/updateContext" + headers = {'Content-Type': 'application/json'} + r = requests.post( + designerUrl, + data=json.dumps( + data.test2), + headers=headers) + r = requests.get(brokerUrl, headers=headers) + # print(r.content) + resp_content = r.content + resInJson = resp_content.decode('utf8').replace("'", '"') + resp = json.loads(resInJson) + if resp["entityId"]["type"] == "DockerImage" and resp["entityId"]["id"] == "test3": + print "\nValidated" + else: + print "\nNot Validated" + assert r.status_code == 200 + + +''' + test registration for topology +''' + + +def test_persistopology(): + brokerUrl = brokerIp + "/ngsi10/entity/test4" + designerUrl = designerIp + "/ngsi10/updateContext" + headers = {'Content-Type': 'application/json'} + r = requests.post( + designerUrl, + data=json.dumps( + data.test3), + headers=headers) + r = requests.get(brokerUrl, headers=headers) + # print(r.content) + resp_content = r.content + resInJson = resp_content.decode('utf8').replace("'", '"') + resp = json.loads(resInJson) + if resp["entityId"]["type"] == "Topology" and resp["entityId"]["id"] == "test4": + print "\nValidated" + else: + print "\nNot Validated" + assert r.status_code == 200 + + +''' + test if entity does not have domainMetaData +''' +'''def test_DomainMetaDataMissing(): + brokerUrl=brokerIp+"/ngsi10/entity/test4" + designerUrl=designerIp+"/ngsi10/updateContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(designerUrl,data=json.dumps(data.test4),headers=headers) + r=requests.get(url,headers=headers) + assert r.status_code == 200 +''' +''' + testCase if entity does not have attribute +''' +'''def test_attributesMissing(): + brokerUrl=brokerIp+"/ngsi10/entity/test5" + designerUrl=designerIp+"/ngsi10/updateContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(designerUrl,data=json.dumps(data.test5),headers=headers) + r=requests.get(url,headers=headers) + assert r.status_code == 200 +''' +''' + test if type of attributes is string +''' +'''def test_stringAttributes(): + brokerUrl=brokerIp+"/ngsi10/entity/test6" + designerUrl=designerIp+"/ngsi10/updateContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(designerUrl,data=json.dumps(data.test6),headers=headers) + r=requests.get(url,headers=headers) + assert r.status_code == 200 +''' +''' + test if value and type of attributes is null +''' +'''def test_nullAttributes(): + brokerUrl=brokerIp+"/ngsi10/entity/test7" + designerUrl=designerIp+"/ngsi10/updateContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(designerUrl,data=json.dumps(data.test7),headers=headers) + r=requests.get(url,headers=headers) + assert r.status_code == 200 +''' + +''' + test if type of domainMetaData is point +''' +'''def test_pointDomainMetaData(): + brokerUrl=brokerIp+"/ngsi10/entity/test8" + designerUrl=designerIp+"/ngsi10/updateContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(designerUrl,data=json.dumps(data.test8),headers=headers) + r=requests.get(url,headers=headers) + assert r.status_code == 200 +''' + +''' + test if data have contextElement(test for curl client) +''' + +'''def test_forCurlClient(): + brokerUrl=brokerIp+"/ngsi10/entity/test9" + designerUrl=designerIp+"/ngsi10/updateContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(designerUrl,data=json.dumps(data.test9),headers=headers) + r=requests.get(url,headers=headers) + assert r.status_code == 200 +''' diff --git a/test/UnitTest/v1/data_ngsi10.py b/test/UnitTest/v1/data_ngsi10.py new file mode 100644 index 00000000..21de4bd6 --- /dev/null +++ b/test/UnitTest/v1/data_ngsi10.py @@ -0,0 +1,1408 @@ + +# Subscription request payload +subdata1=\ +{ + "entities": [ + { + "id": "Result0", + "type": "Result0" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + + +# Payload to create entity with id as Result1 +subdata2=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result1", + "type": "Result1" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe entity Result1 +subdata3=\ +{ + "entities": [ + { + "id": "Result1", + "type": "Result1" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + + +# Payload to create entity with only one attribute and id as Result2 +subdata4=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result2", + "type": "Result2" + }, + "attributes": [ + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe entity Result2 +subdata5=\ +{ + "entities": [ + { + "id": "Result2", + "type": "Result2" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + + +# Payload to crerate entity with only one attribute with id as Result3 +subdata6=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result3", + "type": "Result3" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload to subscribe entity Result3 +subdata7=\ +{ + "entities": [ + { + "id": "Result3", + "type": "Result3" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to create entity without domain metadata with id as Result4 +subdata8=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result4", + "type": "Result4" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload to subscribe entity Result4 +subdata9=\ +{ + "entities": [ + { + "id": "Result4", + "type": "Result4" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + + +# Payload to create entity without attribute, with id as Result5 +subdata10=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result5", + "type": "Result5" + }, + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe the entity Result5 +subdata11=\ +{ + "entities": [ + { + "id": "Result5", + "type": "Result5" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to create entity without any attribute or meta data, with id as Result6 +subdata12=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result6", + "type": "Result6" + } + + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe entity Result6 +subdata13=\ +{ + "entities": [ + { + "id": "Result6", + "type": "Result6" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + + +# Payload to create entity without type, with id as Result7 +subdata14=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result7", + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload to subscribe entity Result7 +subdata15=\ +{ + "entities": [ + { + "id": "Result7", + "type": "Result7" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + + +# Payload to subscribe entity Result8 +subdata16=\ +{ + "entities": [ + { + "id": "Result8", + "type": "Result8" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + + +# Payload to create entity with id as Result9 +subdata17=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result9", + "type": "Result9" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload to create entity with id as Result10 +subdata18=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result10", + "type": "Result10" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe entity Result19 +subdata19=\ +{ + "entities": [ + { + "id": "Result10", + "type": "Result10" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to update entity with different values of attributes of entity Result10 +subdata20=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result10", + "type": "Result10" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 80 + }, + { + "name": "pressure", + "type": "float", + "value": 50 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload to create entity with id as Result11 +subdata21=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result11", + "type": "Result11" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload to subscribe entity Result11 +subdata22=\ +{ + "entities": [ + { + "id": "Result11", + "type": "Result11" + } + ], + "reference": "http://127.0.0.1:1026/v2/op/notify" +} + +# Payload to upload entity with different values of attribute for entity Result11 +subdata23=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result11", + "type": "Result11" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 85 + }, + { + "name": "pressure", + "type": "float", + "value": 50 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload to create entity with id as Result12 +subdata24=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result12", + "type": "Result12" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe entity Result12 +subdata25=\ +{ + "entities": [ + { + "id": "Result12", + "type": "Result12" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to update entity with different values for entity Result12 +subdata26=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result12", + "type": "Result12" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 88 + }, + { + "name": "pressure", + "type": "float", + "value": 52 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to create entity with id as Result13 +subdata27=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result13", + "type": "Result13" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe entity Result13 +subdata28=\ +{ + "entities": [ + { + "id": "Result13", + "type": "Result13" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to update entity with different values of attribute for entity Result13 +subdata29=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result13", + "type": "Result13" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 15 + }, + { + "name": "pressure", + "type": "float", + "value": 20 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to create entity with id as Result14 +subdata30=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result14", + "type": "Result14" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subscribe entity Result14 +subdata31=\ +{ + "entities": [ + { + "id": "Result14", + "type": "Result14" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to update entity with different values of attributes for entity Result14 +subdata32=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result14", + "type": "Result14" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 87 + }, + { + "name": "pressure", + "type": "float", + "value": 55 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to create entity with id as Result15 +subdata33=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result15", + "type": "Result15" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subsribe entity Result15 +subdata34=\ +{ + "entities": [ + { + "id": "Result15", + "type": "Result15" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to update entity with different values of attributes for entity Result15 +subdata35=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result15", + "type": "Result15" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 80 + }, + { + "name": "pressure", + "type": "float", + "value": 57 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to create entity with id as Result16 +subdata36=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result16", + "type": "Result16" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subsribe entity Result16 +subdata37=\ +{ + "entities": [ + { + "id": "Result16", + "type": "Result16" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to update entity with different values of attributes for entity Result16 +subdata38=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result16", + "type": "Result16" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 83 + }, + { + "name": "pressure", + "type": "float", + "value": 59 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to create entity with id as Result17 +subdata39=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result17", + "type": "Result17" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to subsribe entity Result17 +subdata40=\ +{ + "entities": [ + { + "id": "Result17", + "type": "Result17" + } + ], + "reference": "http://0.0.0.0:8888/v2" +} + +# Payload to update entity with different values of attributes for entity Result17 +subdata41=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result17", + "type": "Result17" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 83 + }, + { + "name": "pressure", + "type": "float", + "value": 52 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + +# Payload for querying entity with id Result17 +subdata42=\ +{"entities":[{"id":"Result17"}]} + +# Payload for querying entity whose type is of pattern Result* +subdata43=\ +{"entities":[{"type":"Result*"}]} + +# Payload to query entity with restrictions and scope type Polygon +subdata44=\ +{ + "entities":[ + { + "id":"Result*" + } + ], + "restriction":{ + "scopes":[ + { + "scopeType":"polygon", + "scopeValue":{ + "vertices":[ + { + "latitude":34.4069096565206, + "longitude":135.84594726562503 + }, + { + "latitude":37.18657859524883, + "longitude":135.84594726562503 + }, + { + "latitude":37.18657859524883, + "longitude":141.51489257812503 + }, + { + "latitude":34.4069096565206, + "longitude":141.51489257812503 + }, + { + "latitude":34.4069096565206, + "longitude":135.84594726562503 + } + ] + } + }] + } +} + +# Payload to query entity with restrictions and scope type Circle +subdata45=\ +{ + "entities": [{ + "id": "Result*" + }], + "restriction": { + "scopes": [{ + "scopeType": "circle", + "scopeValue": { + "centerLatitude": 49.406393, + "centerLongitude": 8.684208, + "radius": 10.0 + } + }, { + "scopeType": "stringQuery", + "scopeValue":"city=Heidelberg" + }] + } + } + + +# Payload to create entity with id as Result46 +subdata46=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result46", + "type": "Result46" + }, + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ] +} + +# Payload to create entity with id as Result047 +subdata47=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result047", + "type": "Result047" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 73 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to update the entity Result047 with updateAction as DELETE +subdata48=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result047", + "type": "Result047" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 84 + }, + { + "name": "pressure", + "type": "float", + "value": 55 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "DELETE" +} + +#creating entity using empty payload +subdata49=\ +{ + +} + +# Payload to create entity with id as Result050 +subdata50=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result050", + "type": "Result050" + }, + "attributes": [ + { + "name": "temperature", + "type": "command", + "value": 58 + }, + { + "name": "pressure", + "type": "float", + "value": 44 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to create entity with id as Result048 +subdata51=\ +{ + "contextRegistrations": [ + { + "entities": [ + { + "type": "Lamp", + "id": "Lamp.001" + } + ], + "attributes": [ + { + "name": "on", + "type": "command" + }, + { + "name": "off", + "type": "command" + } + ], + "providingApplication": "http://0.0.0.0:8888" + } + ], + "duration": "P1Y" +} + +# Payload to create entity with id as Result049 with updateAction as Append +subdata52=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result049", + "type": "Result049" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 84 + }, + { + "name": "pressure", + "type": "float", + "value": 55 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "APPEND" +} + +# Payload to create entity with id as Result053 +subdata53=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result053", + "type": "Result053" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 84 + }, + { + "name": "pressure", + "type": "float", + "value": 55 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload to update entity with updateAction equal to DELETE +subdata54=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Result053", + "type": "Result053" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 84 + }, + { + "name": "pressure", + "type": "float", + "value": 55 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "DELETE" +} + +# Payload to check notifyContext +subdata55=\ +{ + "subscriptionId":"q0017b683-490c-490b-b8e5-85d59c1b2b9c", + "originator":"Vehicle100" +} + +# Payload to create entity with id as Test001 +subdata56=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Test001", + "type": "Test001" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 84 + }, + { + "name": "pressure", + "type": "float", + "value": 55 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +# Payload for southbound feature +subdata57=\ +{ + "contextElements": [ + { + "entityId": { + "id": "Lamp.001", + "type": "Lamp" + }, + "attributes": [ + { + "name": "on", + "type": "command", + "value": "" + } + ] + } + ], + "updateAction": "UPDATE" +} + + diff --git a/test/UnitTest/v1/test_casesNGSIv1.py b/test/UnitTest/v1/test_casesNGSIv1.py new file mode 100644 index 00000000..e42f89f8 --- /dev/null +++ b/test/UnitTest/v1/test_casesNGSIv1.py @@ -0,0 +1,1014 @@ +import os,sys +# change the path accoring to the test folder in system +#sys.path.append('/home/ubuntu/setup/src/fogflow/test/UnitTest/v1') +from datetime import datetime +import copy +import json +import requests +import time +import pytest +import data_ngsi10 +import sys + +# change it by broker ip and port +brokerIp="http://localhost:8070" + +print("Testing of v1 API") +# testCase 1 +''' + To test subscription request +''' +def test_getSubscription1(): + url=brokerIp+"/ngsi10/subscribeContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata1),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + assert r.status_code == 200 + + +#testCase 2 +''' + To test entity creation with attributes, then susbscribing and get subscription using ID +''' +def test_getSubscription2(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r = requests.post(url,data=json.dumps(data_ngsi10.subdata2),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata3),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['entities'] + sid2=resp[0]["id"] + if "Result1"==sid2: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 3 +''' + To test entity creation with one attribute : pressure only followed by subscribing and get using ID +''' +def test_getSubscription3(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r = requests.post(url,data=json.dumps(data_ngsi10.subdata4),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata5),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['entities'] + sid2=resp[0]["id"] + if "Result2"==sid2: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 4 +''' + To test entity creation with one attribute : Temperature only followed by subscription and get using ID +''' +def test_getSubscription4(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r = requests.post(url,data=json.dumps(data_ngsi10.subdata6),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata7),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['entities'] + sid2=resp[0]["id"] + if "Result3"==sid2: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 5 +''' + To test create entity without passing Domain data followed by subscription and get using ID +''' +def test_getSubscription5(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r = requests.post(url,data=json.dumps(data_ngsi10.subdata8),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata9),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['entities'] + sid2=resp[0]["id"] + if "Result4"==sid2: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 6 +''' + To test create entity without attributes followed by subscription and get using Id +''' +def test_getSubscription6(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r = requests.post(url,data=json.dumps(data_ngsi10.subdata10),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata11),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['entities'] + sid2=resp[0]["id"] + if "Result5"==sid2: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + + +#testCase 7 +''' + To test create entity without attributes and Metadata and followed by sbscription and get using Id +''' +def test_getSubscription7(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r = requests.post(url,data=json.dumps(data_ngsi10.subdata12),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + #print(r.status_code) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata13),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['entities'] + sid2=resp[0]["id"] + if "Result6"==sid2: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + + +#testCase 8 +''' + To test create entity without entity type followed by subscription and get using Id +''' +def test_getSubscription8(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r = requests.post(url,data=json.dumps(data_ngsi10.subdata14),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata15),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['entities'] + sid2=resp[0]["id"] + if "Result7"==sid2: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + + + +#testCase 9 +''' + To test get subscription request by first posting subscription request followed by delete request +''' +def test_getSubscription9(): + #create an entity + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata16),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #subscribing + url_del=brokerIp+"/ngsi10/subscription/" + url=url_del+sid + r = requests.delete(url,headers=headers) + #print(r.status_code) + + #get request to fetch subscription + get_url=brokerIp+"/ngsi10/subscription" + url=get_url+sid + r=requests.get(url) + print("Subscription with sid-"+sid+" not found") + assert r.status_code == 404 + + +#testCase 10 +''' + To test the update post request to create entity +''' +def test_getSubscription10(): + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata17),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + #print(r.status_code) + assert r.status_code == 200 + +#testCase 11 +''' + To test subscription with attributes and using ID to validate it +''' +def test_getSubscription11(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata18),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata19),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update the created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata20),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + print(r.content) + assert r.status_code == 200 + +#testCase 12 +''' + To test subscription for its if and else part : 1) for Destination Header +''' +def test_getSubscription12(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata21),headers=headers) + resp_content=r.content + resInJson=resp_content.decode('utf8').replace("'",'"') + resp=json.loads(resInJson) + #print(resp) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json','Destination' : 'orion-broker'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata22),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update the created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata23),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://127.0.0.1:1026/v2/entities/" + #r=requests.post(url,json={"subscriptionId" : sid}) + r=requests.get(url) + print(r.content) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #if resp[0]["id"]=="Result11" and resp[0]["type"]=="Result11": + #print("\nValidated") + #else: + #print("\nNot Validated") + assert r.status_code == 200 + +#testCase 13 +''' + To test subscription for its if and else part : 2) for User - Agent Header +''' +def test_getSubscription18(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata24),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json','User-Agent' : 'lightweight-iot-broker'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata25),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata26),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + print(r.content) + assert r.status_code == 200 + +#testCase 14 +''' + To test subcription for its if else part : 3) Require-Reliability Header +''' +def test_getSubscription19(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata27),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json','Require-Reliability' : 'true'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata28),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update the created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata29),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + print(r.content) + assert r.status_code == 200 + +#testCase 15 +''' + To test subscription with two headers simultaneously : 4) Destination and User-Agent +''' +def test_getSubscription20(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata30),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json','Destination' : 'orion-broker','User-Agent':'lightweight-iot-broker'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata31),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata32),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + #print(r.content) + if r.content=="Not validated": + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 16 +''' + To test subscription with two headers simultaneously : 4) User-Agent and Require-Reliability +''' +def test_getSubscription21(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata33),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json','User-Agent':'lightweight-iot-broker','Require-Reliability':'true'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata34),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata35),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + print(r.content) + assert r.status_code == 200 + +#testCase 17 +''' + To test subscription with two headers simultaneously : 4) Destination and Require-Reliability headers +''' +def test_getSubscription22(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata36),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json','Destination':'orion-broker','Require-Reliability':'true'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata37),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata38),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + #print(r.content) + if r.content == "Not validated": + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 18 +''' + To test subscription with all headers simultaneously : 5) Destination, User-Agent and Require-Reliability headers +''' +def test_getSubscription23(): + #create an entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type' : 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata39),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #subscribing + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json','Destination':'orion-broker','User-Agent':'lightweight-iot-broker','Require-Reliability':'true'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata40),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update created entity + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(data_ngsi10.subdata41),headers=headers) + resp_content1=r.content + resInJson=resp_content1.decode('utf8').replace("'",'"') + resp1=json.loads(resInJson) + #print(resp1) + + #validate via accumulator + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + #print(r.content) + if r.content == "Not validated": + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + +#testCase 19 +''' + To test for get subscripton requests +''' +def test_getsubscription24(): + url=brokerIp+"/ngsi10/subscription" + r=requests.get(url) + assert r.status_code == 200 + +#testCase 20 +''' + To test for get all entities +''' +def test_getallentities(): + url=brokerIp+"/ngsi10/entity" + r=requests.get(url) + assert r.status_code == 200 + +#testCase 21 +''' + To test for query request using Id +''' +def test_queryrequest1(): + url=brokerIp+"/ngsi10/queryContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata42),headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 22 +''' + To test for query request using type +''' +def test_queryrequest2(): + url=brokerIp+"/ngsi10/queryContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata43),headers=headers) + #print(r.content) + assert r.status_code == 200 + + +#testCase 23 +''' + To test for query request using geo-scope(polygon) +''' +def test_queryrequest3(): + url=brokerIp+"/ngsi10/queryContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata44),headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 24 +''' + To test for query request multiple filter +''' +def test_queryrequest4(): + url=brokerIp+"/ngsi10/queryContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata45),headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 25 +''' + To test if wrong payload is decoded or not +''' +def test_case25(): + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata46),headers=headers) + #print(r.status_code) + assert r.status_code == 200 + + +#testCase26 +''' + To test the response on passing DELETE in updateAction in payload +''' +def test_case26(): + #create v1 entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json','User-Agent':'lightweight-iot-broker'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata47),headers=headers) + #print(r.content) + + #get the created entity + url=brokerIp+"/ngsi10/entity/Result047" + r=requests.get(url) + #print(r.content) + + #passing DELETE in update payload + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json','User-Agent':'lightweight-iot-broker'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata48),headers=headers) + #print(r.content) + + #get the created entity + url=brokerIp+"/ngsi10/entity/Result047" + r=requests.get(url) + #print(r.content) + assert r.status_code == 404 + +#testCase 27 +''' + To test the entity creation with empty payload +''' +def test_case27(): + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata49),headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 28 +''' + To test the subscription with empty payload +''' +def test_case28(): + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata49),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + assert r.status_code == 200 + +#testCase 29 +''' + To get subscription of empty payload when subscribing +''' +def test_case29(): + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata49),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(resp) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #get request + get_url=brokerIp+"/ngsi10/subscription/" + url=get_url+sid + r=requests.get(url) + #print(r.content) + assert r.status_code == 200 + +#testCase 30 +''' + To test the action of API on passing an attribute as a command in payload +''' +def test_cases30(): + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json','User-Agent':'lightweight-iot-broker'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata50),headers=headers) + #print(r.content) + assert r.status_code == 500 + +#testCase 31 +''' + To test the fiware header with updateAction equal to UPDATE +''' +def test_case31(): + #create and register entity + url=brokerIp+"/NGSI9/registerContext" + headers={'Content-Type':'appliction/json','fiware-service':'openiot','fiware-servicepath':'/'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata51),headers=headers) + #print(r.content) + + # maiing a updateContext request + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json','fiware-service':'openiot','fiware-servicepath':'/'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata57),headers=headers) + #print(r.content) + assert r.status_code == 200 + + +#testCase 32 +''' + To test the fiware header with updateAction equal to APPEND +''' +def test_case32(): + url=brokerIp+"/NGSI9/registerContext" + headers={'Content-Type':'appliction/json','fiware-service':'openiot','fiware-servicepath':'/'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata52),headers=headers) + #print(r.content) + + # maiing a updateContext request + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json','fiware-service':'openiot','fiware-servicepath':'/'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata57),headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 33 +''' + To test the fiware header with updateAction equal to delete +''' +def test_case33(): + #create v1 entity + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json','fiware-service':'Abc','fiware-servicepath':'pqr'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata53),headers=headers) + #print(r.content) + + #get the created entity + url=brokerIp+"/ngsi10/entity/Result053" + r=requests.get(url) + #print(r.content) + + #passing DELETE in update payload + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'appliction/json','fiware-service':'Abc','fiware-servicepath':'pqr'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata54),headers=headers) + #print(r.content) + + #get the created entity + url=brokerIp+"/ngsi10/entity/Result053" + r=requests.get(url) + #print(r.content) + assert r.status_code == 404 + +#testCase 34 +''' + To test the notifyContext request +''' +def test_case34(): + url=brokerIp+"/ngsi10/notifyContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata55),headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 35 +''' + To test unsubscribing feature +''' +def test_case35(): + #create subscription + url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata56),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #unsubscribe Context + url=brokerIp+"/ngsi10/unsubscribeContext" + headers={'Content-Type': 'application/json'} + r=requests.post(url,json={"subscriptionId":sid,"originator":"POMN"},headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 36 +''' + To test entity creation using other route +''' +def test_case36(): + url=brokerIp+"/v1/updateContext" + headers={'Content-Type':'appliction/json'} + r=requests.post(url,data=json.dumps(data_ngsi10.subdata56),headers=headers) + #print(r.content) + assert r.status_code == 200 + +#testCase 37 +''' + To test and fetch unique entity +''' +def test_case37(): + url=brokerIp+"/ngsi10/entity/Result14" + r=requests.get(url) + #print(r.content) + assert r.status_code == 200 + +#testCase 38 +''' + To test and fetch attribute specific to an entity +''' +def test_case38(): + url=brokerIp+"/ngsi10/entity/Result14/pressure" + r=requests.get(url) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + #print(r.content) + name=resp['name'] + type1=resp['type'] + val=resp['value'] + if name=='pressure' and type1=='float' and val==55: + print("\nValidated") + else: + print("\nNot Validated") + assert r.status_code == 200 + + diff --git a/test/UnitTest/v2/test_casesNGSIv2.py b/test/UnitTest/v2/test_casesNGSIv2.py new file mode 100644 index 00000000..bdfac448 --- /dev/null +++ b/test/UnitTest/v2/test_casesNGSIv2.py @@ -0,0 +1,302 @@ +import os,sys +# change the path accoring to the test folder in system +#sys.path.append('/home/ubuntu/setup/src/fogflow/test/UnitTest/v2') +from datetime import datetime +import copy +import json +import requests +import time +import pytest +import v2data +import sys + + +# change it by broker ip and port +brokerIp="http://localhost:8070" + +print(" The Validation test begins ") + +# testCase 1 +''' + Testing get all subscription +''' +def test_getAllSubscription(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + r=requests.get(url,headers=headers) + assert r.status_code == 200 + + +#testCase 2 + +''' + Testing get subscription by Id +''' + +def test_getSubscriptionById(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(v2data.subscription_data),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + get_url=brokerIp+"/v2/subscription/" + url=get_url+sid + r=requests.get(url,headers=headers) + assert r.status_code == 200 + print("Get subscription by Id testcase passed") + + +#testCase 3 +''' + Testing get subscription by nil id +''' + +def test_getSubscriptionByNilId(): + get_url=brokerIp+"/v2/subscription/nil" + headers= {'Content-Type': 'application/json'} + r=requests.get(get_url,headers=headers) + assert r.status_code == 404 + +# Test Delete subscription id subscriptionId is persent in the broker +#testCase 4 + +''' + Test delete subscription by Id +''' + +def test_deleteSubscriptionById(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(v2data.subscription_data),headers=headers) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + get_url=brokerIp+"/v2/subscription/" + url=get_url+sid + r=requests.delete(url,headers=headers) + assert r.status_code == 200 + + + +#testCase 5 +''' +Test if subscriptionId is not persent in the broker +''' + +def test_deleteSubscriptionId(): + delete_url=brokerIp+"/v2/subscription/nil" + headers= {'Content-Type': 'application/json'} + r=requests.delete(delete_url,headers=headers) + assert r.status_code == 200 + +#testCase 6 +''' + Test with wrong payload +''' + +def test_subscriptionWithWrongPayload(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(v2data.subscriptionWrongPaylaod),headers=headers) + assert r.status_code == 500 + +#testCase 7 +''' + Testing subscription response with the same entity in ngsiv1 and ngsiv2. +''' + +def test_v1v2SubscriptionForSameEntity(): + V2url=brokerIp+"/v2/subscriptions" + ngsi10url=brokerIp+"/ngsi10/subscribeContext" + headers= {'Content-Type': 'application/json'} + V2=requests.post(V2url,data=json.dumps(v2data.subscription_data),headers=headers) + ngsi10=requests.post(ngsi10url,data=json.dumps(v2data.v1SubData),headers=headers) + assert V2.status_code == 201 + assert ngsi10.status_code == 200 + +#testCase 8 +''' + Test ngsiv2 subscription +''' + +def test_Subscription(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + response=requests.post(url,data=json.dumps(v2data.subscription_data),headers=headers) + assert response.status_code==201 + +#testCase 9 +#update request wit create action + +''' + Testing update request with update action +''' +def test_update_request_with_update_action(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + subresponse=requests.post(url,data=json.dumps(v2data.subscription_data),headers=headers) + updateresponse=requests.post(url,data=json.dumps(v2data.updateDataWithupdateaction),headers=headers) + assert updateresponse.status_code == 201 + +#testCase 10 +''' + Testing update request with Delete request +''' + +def test_upadte_request_with_delete_action(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + subresponse=requests.post(url,data=json.dumps(v2data.subscription_data),headers=headers) + updateresponse=requests.post(url,data=json.dumps(v2data.deleteDataWithupdateaction),headers=headers) + assert updateresponse.status_code==201 + + +#testCase 11 + +''' + Testing update request with create action +''' + +def test_update_request_with_create_action(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + subresponse=requests.post(url,data=json.dumps(v2data.subscription_data),headers=headers) + updateresponse=requests.post(url,data=json.dumps(v2data.createDataWithupdateaction),headers=headers) + assert updateresponse.status_code==201 + +#testCase 12 +''' + Testing notification send by broker +''' + +def test_notifyOneSubscriberv2WithCurrentStatus(): + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + updateresponse=requests.post(url,data=json.dumps(v2data.createDataWithupdateaction),headers=headers) + subresponse=requests.post(url,data=json.dumps(v2data.subscription_data),headers=headers) + assert subresponse.status_code==201 + +# testCase 13 +''' + Testing subscription with attributes and using ID +''' +def test_getSubscription1validation(): + #update request to create entity at broker + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'application/json'} + r=requests.post(url,data=json.dumps(v2data.subdata1),headers=headers) + #print(r.status_code) + #print(r.content) + + #subsciption request in v2 format + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json'} + r=requests.post(url,data=json.dumps(v2data.subdata2),headers=headers) + #print(r.status_code) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update to trigger notification + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(v2data.subdata3),headers=headers) + #print(r.status_code) + #print(r.content) + + #validation based on subscriptionId + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + print(r.content) + assert r.status_code == 200 + + +# testCase 14 +''' + Testing subscription with attributes and with Header : User-Agent +''' +def test_getsubscription2validate(): + #update request to create entity at broker + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'application/json'} + r=requests.post(url,data=json.dumps(v2data.subdata4),headers=headers) + #print(r.status_code) + #print(r.content) + + #subsciption request in v2 format + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json','User-Agent':'lightweight-iot-broker'} + r=requests.post(url,data=json.dumps(v2data.subdata5),headers=headers) + #print(r.status_code) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update to trigger notification + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(v2data.subdata6),headers=headers) + #print(r.status_code) + #print(r.content) + + #vaidation based on subscriptionId + url="http://0.0.0.0:8888/validateNotification" + r=requests.post(url,json={"subscriptionId" : sid}) + print(r.content) + assert r.status_code == 200 + + +# testCase 15 +''' + Testing with subscribing , updating and deleting and validating +''' +def test_getsubscription3(): + #update request to create entity at broker + url=brokerIp+"/ngsi10/updateContext" + headers={'Content-Type':'application/json'} + r=requests.post(url,data=json.dumps(v2data.subdata7),headers=headers) + #print(r.status_code) + #print(r.content) + + #subsciption request in v2 format + url=brokerIp+"/v2/subscriptions" + headers= {'Content-Type': 'application/json','User-Agent':'lightweight-iot-broker'} + r=requests.post(url,data=json.dumps(v2data.subdata8),headers=headers) + #print(r.status_code) + resp_content=r.content + resInJson= resp_content.decode('utf8').replace("'", '"') + resp=json.loads(resInJson) + resp=resp['subscribeResponse'] + sid=resp['subscriptionId'] + #print(sid) + + #update to trigger Notification + url=brokerIp+"/ngsi10/updateContext" + r=requests.post(url,data=json.dumps(v2data.subdata9),headers=headers) + #print(r.status_code) + #print(r.content) + + #delete the subscription + url=brokerIp+"/v2/subscription/"+sid + r=requests.delete(url) + #print(r.status_code) + #print(r.content) + print("The subscriptionId "+sid+" is deleted successfully") + + #validate based on get subscriptionId + url=brokerIp+"/v2/subscription/"+sid + r=requests.get(url) + #print(r.status_code) + #print(r.content) + assert r.status_code == 404 + print("The subscriptionId "+sid+" coud not be fetched via get since deleted") + diff --git a/test/UnitTest/v2/v2data.py b/test/UnitTest/v2/v2data.py new file mode 100644 index 00000000..b91460b2 --- /dev/null +++ b/test/UnitTest/v2/v2data.py @@ -0,0 +1,504 @@ +subscription_data=\ +{ + "description": "A subscription to get info about Room1", + "subject": { + "entities": [ + { + "id": "Room1", + "type": "Room", + } + ], + "condition": { + "attrs": [ + "p3" + ] + } + }, + "notification": { + "http": { + "url": "http://0.0.0.0:8888/accumulate" + }, + "attrs": [ + "p1", + "p2", + "p3" + ] + }, + "expires": "2040-01-01T14:00:00.00Z", + "throttling": 5 +} + + + +#data to test the following code for broker.thinBroker.go:946 +''' + subReqv2 := SubscriptionRequest{} + err := r.DecodeJsonPayload(&subReqv2) + if err != nil { + rest.Error(w, err.Error(), http.StatusInternalServerError) + return + } +''' + +subscriptionWrongPaylaod=\ +{ + "description": "A subscription to get info about Room1", + "subject": { + "entities": [ + { + "id": "Room1", + "type": "Room", + "ispattern":"false" + } + ], + "condition": { + "attrs": [ + "p3" + ] + } + }, + "notification": { + "http": { + "url": "http://0.0.0.0:8888/accumulate" + }, + "attrs": [ + "p1", + "p2", + "p3" + ] + }, + "expires": "2040-01-01T14:00:00.00Z", + "throttling": 5 +} + +v1SubData=\ +{ + "entities": [ + { + "id": "Room1", + "type": "Room", + } + ], + "reference": "http://0.0.0.0:8888/accumulate" +} + +updateDataWithupdateaction=\ +{ +"contextElements": [ +{ +"entityId": { +"id": "Room1", +"type": "Room" +}, +"attributes": [ +{ +"name": "p1", +"type": "float", +"value": 60 +}, +{ +"name": "p3", +"type": "float", +"value": 69 +}, +{ +"name": "p2", +"type": "float", +"value": 32 +} +], +"domainMetadata": [ +{ +"name": "location", +"type": "point", +"value": { +"latitude": 49.406393, +"longitude": 8.684208 +} +} +] +} +], +"updateAction": "UPDATE" +} + +createDataWithupdateaction=\ +{ +"contextElements": [ +{ +"entityId": { +"id": "Room1", +"type": "Room" +}, +"attributes": [ +{ +"name": "p1", +"type": "float", +"value": 90 +}, +{ +"name": "p3", +"type": "float", +"value": 70 +}, +{ +"name": "p2", +"type": "float", +"value": 12 +} +], +"domainMetadata": [ +{ +"name": "location", +"type": "point", +"value": { +"latitude": 49.406393, +"longitude": 8.684208 +} +} +] +} +], +"updateAction": "CRETAE" +} + +deleteDataWithupdateaction=\ +{ +"contextElements": [ +{ +"entityId": { +"id": "Room1", +"type": "Room" +}, +"attributes": [ +{ +"name": "p1", +"type": "float", +"value": 12 +}, +{ +"name": "p3", +"type": "float", +"value": 13 +}, +{ +"name": "p2", +"type": "float", +"value": 14 +} +], +"domainMetadata": [ +{ +"name": "location", +"type": "point", +"value": { +"latitude": 49.406393, +"longitude": 8.684208 +} +} +] +} +], +"updateAction": "DELETE" +} + +subdata1=\ +{ + "contextElements": [ + { + "entityId": { + "id": "RoomTrial10", + "type": "Room" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 69 + }, + { + "name": "pressure", + "type": "float", + "value": 75 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +subdata2=\ +{ + "description": "A subscription to get info about RoomTrial10", + "subject": { + "entities": [ + { + "id": "RoomTrial10", + "type": "Room" + } + ], + "condition": { + "attrs": [ + "pressure" + ] + } + }, + "notification": { + "http": { + "url": "http://0.0.0.0:8888/accumulate" + }, + "attrs": [ + "temperature" + ] + }, + "expires": "2040-01-01T14:00:00.00Z", + "throttling": 5 +} + +subdata3=\ +{ + "contextElements": [ + { + "entityId": { + "id": "RoomTrial10", + "type": "Room" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 50 + }, + { + "name": "pressure", + "type": "float", + "value": 80 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + +subdata4=\ +{ + "contextElements": [ + { + "entityId": { + "id": "RoomTrial20", + "type": "Room" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 69 + }, + { + "name": "pressure", + "type": "float", + "value": 75 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + + +subdata5=\ +{ + "description": "A subscription to get info about RoomTrial20", + "subject": { + "entities": [ + { + "id": "RoomTrial20", + "type": "Room" + } + ], + "condition": { + "attrs": [ + "pressure" + ] + } + }, + "notification": { + "http": { + "url": "http://0.0.0.0:8888/accumulate" + }, + "attrs": [ + "temperature" + ] + }, + "expires": "2040-01-01T14:00:00.00Z", + "throttling": 5 +} + + + +subdata6=\ +{ + "contextElements": [ + { + "entityId": { + "id": "RoomTrial20", + "type": "Room" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 40 + }, + { + "name": "pressure", + "type": "float", + "value": 85 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + + +subdata7=\ +{ + "contextElements": [ + { + "entityId": { + "id": "RoomTrial30", + "type": "Room" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 69 + }, + { + "name": "pressure", + "type": "float", + "value": 75 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + + + +subdata8=\ +{ + "description": "A subscription to get info about RoomTrial30", + "subject": { + "entities": [ + { + "id": "RoomTrial30", + "type": "Room" + } + ], + "condition": { + "attrs": [ + "pressure" + ] + } + }, + "notification": { + "http": { + "url": "http://0.0.0.0:8888/accumulate" + }, + "attrs": [ + "temperature" + ] + }, + "expires": "2040-01-01T14:00:00.00Z", + "throttling": 5 +} + + + + + +subdata9=\ +{ + "contextElements": [ + { + "entityId": { + "id": "RoomTrial30", + "type": "Room" + }, + "attributes": [ + { + "name": "temperature", + "type": "float", + "value": 44 + }, + { + "name": "pressure", + "type": "float", + "value": 60 + } + ], + "domainMetadata": [ + { + "name": "location", + "type": "point", + "value": { + "latitude": -33.1, + "longitude": -1.1 + }} + ] + } + ], + "updateAction": "UPDATE" +} + diff --git a/test/jmeter/GetPersistData.jmx b/test/jmeter/GetPersistData.jmx new file mode 100644 index 00000000..090902ac --- /dev/null +++ b/test/jmeter/GetPersistData.jmx @@ -0,0 +1,556 @@ + + + + + + false + true + false + + + + + + + + continue + + false + 1 + + 10 + 1 + false + + + true + + + + + + + 180.179.214.208 + 8070 + http + + /ngsi10/entity/test10 + GET + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + + + 180.179.214.208 + 8070 + http + + /ngsi10/entity/test11 + GET + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + + + 180.179.214.208 + 8070 + http + + /ngsi10/entity/test12 + GET + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + + + 180.179.214.208 + 8070 + http + + /ngsi10/entity/test13 + GET + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + + diff --git a/test/jmeter/PersistData.jmx b/test/jmeter/PersistData.jmx new file mode 100644 index 00000000..b79d374d --- /dev/null +++ b/test/jmeter/PersistData.jmx @@ -0,0 +1,885 @@ + + + + + + false + true + false + + + + + + + + + + Content-Type + application/json + + + + + + continue + + false + 1 + + 10 + 1 + false + + + true + + + + true + + + + false + { + "entityId": { + "type": "Operator", + "id": "test10" + }, + "attributes": [{ + "name": "designboard", + "type": "object", + "value": {} + }, { + "name": "operator", + "type": "object", + "value": { + "description": "", + "name": "recommender", + "parameters": [] + } + }], + "domainMetadata": [{ + "name": "location", + "type": "global", + "value": "global" + }] +} + = + + + + 180.179.214.208 + 8080 + http + + /ngsi10/updateContext + POST + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + true + + + + false + { + "entityId": { + "type": "Topology", + "id": "test11" + }, + "attributes": [{ + "name": "status", + "type": "string", + "value": "enabled" + }, { + "name": "designboard", + "type": "object", + "value": { + "blocks": [{ + "id": 1, + "module": null, + "type": "Task", + "values": { + "name": "Counting", + "operator": "counter", + "outputs": ["Stat"] + }, + "x": 202, + "y": -146 + }, { + "id": 2, + "module": null, + "type": "Task", + "values": { + "name": "Detector", + "operator": "anomaly", + "outputs": ["Anomaly"] + }, + "x": -194, + "y": -134 + }, { + "id": 3, + "module": null, + "type": "Shuffle", + "values": { + "groupby": "ALL", + "selectedattributes": ["all"] + }, + "x": 4, + "y": -18 + }, { + "id": 4, + "module": null, + "type": "EntityStream", + "values": { + "groupby": "EntityID", + "scoped": true, + "selectedattributes": ["all"], + "selectedtype": "PowerPanel" + }, + "x": -447, + "y": -179 + }, { + "id": 5, + "module": null, + "type": "EntityStream", + "values": { + "groupby": "ALL", + "scoped": false, + "selectedattributes": ["all"], + "selectedtype": "Rule" + }, + "x": -438, + "y": -5 + }], + "edges": [{ + "block1": 3, + "block2": 1, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 2 + }, { + "block1": 2, + "block2": 3, + "connector1": ["outputs", "output", 0], + "connector2": ["in", "input"], + "id": 3 + }, { + "block1": 4, + "block2": 2, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 4 + }, { + "block1": 5, + "block2": 2, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 5 + }] + } + }, { + "name": "template", + "type": "object", + "value": { + "description": "detect anomaly events in shops", + "name": "anomaly-detection", + "tasks": [{ + "input_streams": [{ + "groupby": "ALL", + "scoped": true, + "selected_attributes": [], + "selected_type": "Anomaly" + }], + "name": "Counting", + "operator": "counter", + "output_streams": [{ + "entity_type": "Stat" + }] + }, { + "input_streams": [{ + "groupby": "EntityID", + "scoped": true, + "selected_attributes": [], + "selected_type": "PowerPanel" + }, { + "groupby": "ALL", + "scoped": false, + "selected_attributes": [], + "selected_type": "Rule" + }], + "name": "Detector", + "operator": "anomaly", + "output_streams": [{ + "entity_type": "Anomaly" + }] + }] + } + }], + "domainMetadata": [{ + "name": "location", + "type": "global", + "value": "global" + }] +} + = + + + + 180.179.214.208 + 8080 + http + + /ngsi10/updateContext + POST + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + true + + + + false + { + "entityId": { + "type": "DockerImage", + "id": "test12" + }, + "attributes": [{ + "name": "image", + "type": "string", + "value": "fogflow/counter" + }, { + "name": "tag", + "type": "string", + "value": "latest" + }, { + "name": "hwType", + "type": "string", + "value": "X86" + }, { + "name": "osType", + "type": "string", + "value": "Linux" + }, { + "name": "operator", + "type": "string", + "value": "counter" + }, { + "name": "prefetched", + "type": "boolean", + "value": false + }], + "domainMetadata": [{ + "name": "operator", + "type": "string", + "value": "counter" + }, { + "name": "location", + "type": "global", + "value": "global" + }] +} + = + + + + 180.179.214.208 + 8080 + http + + /ngsi10/updateContext + POST + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + true + + + + false + { + "entityId": { + "type": "FogFunction", + "id": "test13" + }, + "attributes": [{ + "name": "name", + "type": "string", + "value": "Test" + }, { + "name": "topology", + "type": "object", + "value": { + "description": "just for a simple test", + "name": "Test", + "tasks": [{ + "input_streams": [{ + "groupby": "EntityID", + "scoped": false, + "selected_attributes": [], + "selected_type": "Temperature" + }], + "name": "Main", + "operator": "dummy", + "output_streams": [{ + "entity_type": "Out" + }] + }] + } + }, { + "name": "designboard", + "type": "object", + "value": { + "blocks": [{ + "id": 1, + "module": null, + "type": "Task", + "values": { + "name": "Main", + "operator": "dummy", + "outputs": ["Out"] + }, + "x": 123, + "y": -99 + }, { + "id": 2, + "module": null, + "type": "EntityStream", + "values": { + "groupby": "EntityID", + "scoped": false, + "selectedattributes": ["all"], + "selectedtype": "Temperature" + }, + "x": -194, + "y": -97 + }], + "edges": [{ + "block1": 2, + "block2": 1, + "connector1": ["stream", "output"], + "connector2": ["streams", "input"], + "id": 1 + }] + } + }, { + "name": "intent", + "type": "object", + "value": { + "geoscope": { + "scopeType": "global", + "scopeValue": "global" + }, + "priority": { + "exclusive": false, + "level": 0 + }, + "qos": "Max Throughput", + "topology": "Test" + } + }, { + "name": "status", + "type": "string", + "value": "enabled" + }], + "domainMetadata": [{ + "name": "location", + "type": "global", + "value": "global" + }] +} + + = + + + + 180.179.214.208 + 8080 + http + + /ngsi10/updateContext + POST + true + false + true + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + + diff --git a/worker/executor.go b/worker/executor.go index 1cc47d68..11d7fa9e 100644 --- a/worker/executor.go +++ b/worker/executor.go @@ -10,6 +10,7 @@ import ( "os" "runtime" "strconv" + "strings" "sync" "github.com/sethgrid/pester" @@ -285,7 +286,8 @@ func (e *Executor) findFreePortNumber() int { func (e *Executor) LaunchTask(task *ScheduledTaskInstance) bool { dockerImage := task.DockerImage - + fmt.Println("=========This is task============") + fmt.Println(task) INFO.Println("to execute Task ", task.ID, " to perform Operation ", dockerImage) if e.workerCfg.Worker.StartActualTask == false { // just for the performance evaluation of Topology Master @@ -388,13 +390,28 @@ func (e *Executor) LaunchTask(task *ScheduledTaskInstance) bool { taskCtx.Subscriptions = make([]string, 0) for _, inputStream := range task.Inputs { - subID, err := e.subscribeInputStream(freePort, &inputStream) - if err == nil { - DEBUG.Println("===========subID = ", subID) - taskCtx.Subscriptions = append(taskCtx.Subscriptions, subID) - taskCtx.EntityID2SubID[inputStream.ID] = subID - } else { - ERROR.Println(err) + NGSILD := e.queryForNGSILdEntity(inputStream.ID) + if NGSILD == 200 { + fmt.Println(&inputStream) + subID, err := e.subscribeLdInputStream(freePort, &inputStream) + if err == nil { + DEBUG.Println("===========subID = ", subID) + taskCtx.Subscriptions = append(taskCtx.Subscriptions, subID) + taskCtx.EntityID2SubID[inputStream.ID] = subID + } else { + ERROR.Println(err) + } + } + NGSIV1 := e.queryForNGSIV1Entity(inputStream.ID) + if NGSIV1 == 200 { + subID, err := e.subscribeInputStream(freePort, &inputStream) + if err == nil { + DEBUG.Println("===========subID = ", subID) + taskCtx.Subscriptions = append(taskCtx.Subscriptions, subID) + taskCtx.EntityID2SubID[inputStream.ID] = subID + } else { + ERROR.Println(err) + } } } @@ -411,6 +428,31 @@ func (e *Executor) LaunchTask(task *ScheduledTaskInstance) bool { return true } +//Query for NGSILD Entity with entityId +func (e *Executor) queryForNGSILdEntity(eid string) int { + if eid == "" { + return 404 + } + brokerURL := e.brokerURL + brokerURL = strings.TrimSuffix(brokerURL, "/ngsi10") + client := NGSI10Client{IoTBrokerURL: brokerURL, SecurityCfg: &e.workerCfg.HTTPS} + statusCode := client.QueryForNGSILDEntity(eid) + fmt.Println(statusCode) + return statusCode +} + +// Query for NGSIV1 Entity with EntityId +func (e *Executor) queryForNGSIV1Entity(eid string) int { + if eid == "" { + return 200 + } + fmt.Println(e.brokerURL) + client := NGSI10Client{IoTBrokerURL: e.brokerURL, SecurityCfg: &e.workerCfg.HTTPS} + statusCode := client.QueryForNGSIV1Entity(eid) + fmt.Println(statusCode) + return statusCode +} + func (e *Executor) registerEndPointService(serviceName string, taskID string, operateName string, ipAddr string, port string, location PhysicalLocation) EntityId { ctxObj := ContextObject{} @@ -530,7 +572,42 @@ func (e *Executor) deregisterTask(taskID string) { } } +// Subscribe for NGSILD input stream +func (e *Executor) subscribeLdInputStream(agentPort string, inputStream *InputStream) (string, error) { + LdSubscription := LDSubscriptionRequest{} + + newEntity := EntityId{} + + if len(inputStream.ID) > 0 { // for a specific context entity + newEntity.Type = inputStream.Type + newEntity.ID = inputStream.ID + } else { // for all context entities with a specific type + newEntity.Type = inputStream.Type + } + + LdSubscription.Entities = make([]EntityId, 0) + LdSubscription.Entities = append(LdSubscription.Entities, newEntity) + LdSubscription.Type = "Subscription" + LdSubscription.WatchedAttributes = inputStream.AttributeList + + LdSubscription.Notification.Endpoint.URI = "http://" + e.workerCfg.InternalIP + ":" + agentPort + "/notifyContext" + + DEBUG.Printf(" =========== issue the following subscription =========== %+v\r\n", LdSubscription) + brokerURL := e.brokerURL + brokerURL = strings.TrimSuffix(brokerURL, "/ngsi10") + client := NGSI10Client{IoTBrokerURL: brokerURL, SecurityCfg: &e.workerCfg.HTTPS} + sid, err := client.SubscribeLdContext(&LdSubscription, true) + if err != nil { + ERROR.Println(err) + return "", err + } else { + return sid, nil + } +} + +//Subscribe for NGSIV1 input stream func (e *Executor) subscribeInputStream(agentPort string, inputStream *InputStream) (string, error) { + fmt.Println("====================Subscription here ===================") subscription := SubscribeContextRequest{} newEntity := EntityId{} @@ -725,14 +802,28 @@ func (e *Executor) onAddInput(flow *FlowInfo) { if e.workerCfg.Worker.StartActualTask == false { return } - - subID, err := e.subscribeInputStream(taskCtx.ListeningPort, &flow.InputStream) - if err == nil { - DEBUG.Println("===========subscribe new input = ", flow, " , subID = ", subID) - taskCtx.Subscriptions = append(taskCtx.Subscriptions, subID) - taskCtx.EntityID2SubID[flow.InputStream.ID] = subID - } else { - ERROR.Println(err) + Id := flow.InputStream.ID + NGSILD := e.queryForNGSILdEntity(Id) + if NGSILD == 200 { + subID, err := e.subscribeLdInputStream(taskCtx.ListeningPort, &flow.InputStream) + if err == nil { + DEBUG.Println("===========subscribe new input = ", flow, " , subID = ", subID) + taskCtx.Subscriptions = append(taskCtx.Subscriptions, subID) + taskCtx.EntityID2SubID[flow.InputStream.ID] = subID + } else { + ERROR.Println(err) + } + } + NGSIV1 := e.queryForNGSIV1Entity(Id) + if NGSIV1 == 200 { + subID, err := e.subscribeInputStream(taskCtx.ListeningPort, &flow.InputStream) + if err == nil { + DEBUG.Println("===========subscribe new input = ", flow, " , subID = ", subID) + taskCtx.Subscriptions = append(taskCtx.Subscriptions, subID) + taskCtx.EntityID2SubID[flow.InputStream.ID] = subID + } else { + ERROR.Println(err) + } } }