diff --git a/docker/smartdatacontext-test/Dockerfile b/docker/smartdatacontext-test/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..3047a8612c15805b129dca4415c63edc84130e30 --- /dev/null +++ b/docker/smartdatacontext-test/Dockerfile @@ -0,0 +1,15 @@ +# Use an official Python runtime as a base image +FROM python:3.11-slim + +# Set the working directory in the container +WORKDIR /app + +# Copy the current directory contents into the container at /usr/src/app +COPY test.pem /app/client.pem +COPY test.key /app/client.key +COPY test-ingress.py /app/ +COPY client.py /app/ +COPY main.py /app/ + +# Install any needed packages specified in requirements.txt +RUN pip install requests \ No newline at end of file diff --git a/docker/smartdatacontext-test/client.py b/docker/smartdatacontext-test/client.py new file mode 100644 index 0000000000000000000000000000000000000000..f09d221a5d4f2d074985c06b73d795e504b8d4dd --- /dev/null +++ b/docker/smartdatacontext-test/client.py @@ -0,0 +1,213 @@ +import mimetypes +import os + +import requests +import json + +class SmartDataContextAPIClient: + def __init__(self, cert_file, key_file, url="https://iot.lisha.ufsc.br/", verifyCertificate=True, logfile=None): + if not url.endswith('/'): + url += '/' + self._url = url + self._client = requests.Session() + self._client.cert = (cert_file, key_file) + self._client.verify = verifyCertificate + self._logfile = logfile + if not verifyCertificate: + requests.urllib3.disable_warnings() + + def _log_request(self, method, url, headers, request_body, response_body): + if self._logfile: + with open(self._logfile, 'a') as log_file: + log_file.write(f"URL: {method} {url}\n") + log_file.write(f"Headers: {json.dumps(headers, indent=2)}\n") + if request_body: + log_file.write(f"Request Body: {json.dumps(request_body, indent=2)}\n") + log_file.write(f"Response Body: {response_body}\n") + log_file.write("\n" + "-"*50 + "\n") + + def _doJsonPost(self, json_data, return_attribute = '', endpoint="api/v1_1/context.php"): + headers = { + 'Content-Type': 'application/json' + } + url = self._url + endpoint + response = self._client.post(url, headers=headers, json=json_data) + try: + response_json = response.json() + self._log_request("POST", url, headers, json_data, response.json()) + if "errors" in response_json: + raise Exception(f"Error processing request: {json.dumps(response_json['errors'])}") + else: + if return_attribute: + return response_json['result'][return_attribute] + else: + return response_json['result'] + except Exception as e: + raise Exception(f"Invalid response from API: {response.content} - {e}") + + def createSmartDataContext(self, content, features, t0=-1, t1=-1, smartDataSources=[], smartDataUnits=[]): + json_request = { + "command": "/create", + "request": { + "content": content, + "features": features, + "t0": t0, + "t1": t1, + "smartDataUnits": smartDataUnits, + "smartDataSouces": smartDataSources + } + } + return self._doJsonPost(json_request, 'smartDataContextId') + + + def associateSmartDataContext(self, smartDataContextIds, smartDataUnits=[], smartDataSources=[]): + if len(smartDataSources) == 0 and len(smartDataUnits) == 0: + raise Exception("At least one smartDataSource or smartDataUnit must be informed") + json_request = { + "command": "/associate", + "request": { + "smartDataContextIds": smartDataContextIds, + "smartDataUnits": smartDataUnits, + "smartDataSources": smartDataSources + } + } + + result = self._doJsonPost(json_request) + if len(result) == 1: + return result[0] + else: + return result + + def unassociateSmartDataContext(self, smartDataContextIds, smartDataUnits=[], smartDataSources=[]): + if len(smartDataSources) == 0 and len(smartDataUnits) == 0: + raise Exception("At least one smartDataSource or smartDataUnit must be informed") + json_request = { + "command": "/unassociate", + "request": { + "smartDataContextIds": smartDataContextIds, + "smartDataUnits": smartDataUnits, + "smartDataSources": smartDataSources + } + } + + result = self._doJsonPost(json_request) + if len(result) == 1: + return result[0] + else: + return result + + def getSmartDataContext(self, smartDataContextId): + json_request = { + "command": "/get", + "request": { + "smartDataContextId": smartDataContextId + } + } + + return self._doJsonPost(json_request) + + def findSmartDataContext(self, smartDataUnits=[], smartDataSources=[], t0=None, t1=None): + if len(smartDataSources) == 0 and len(smartDataUnits) == 0: + raise Exception("At least one smartDataSource or smartDataUnit must be informed") + json_request = { + "command": "/contexts", + "request": { + "smartDataUnits": smartDataUnits, + "smartDataSources": smartDataSources + } + } + if t0: + json_request['request']['t0'] = t0 + if t1: + json_request['request']['t1'] = t1 + + return self._doJsonPost(json_request) + + def querySmartDataContext(self, query): + json_request = { + "command": "/query", + "request": query + } + return self._doJsonPost(json_request) + + def updateSmartDataContext(self, id, content=None, features=None, t0=None, t1=None, smartDataSources=None, smartDataUnits=None): + json_request = { + "command": "/update", + "request": { + "smartDataContextId": id + } + } + if content: + json_request['request']['content'] = content + if features: + json_request['request']['features'] = features + if t0: + json_request['request']['t0'] = t0 + if t1: + json_request['request']['t1'] = t1 + if smartDataSources: + json_request['request']['smartDataSources'] = smartDataSources + if smartDataUnits: + json_request['request']['smartDataUnits'] = smartDataUnits + + return self._doJsonPost(json_request) + + def addUnstructuredDataFromFile(self, smartDataContextId, filePath, fileName=None, mimeType=None): + if not fileName: + fileName = os.path.basename(filePath) + if not mimeType: + mimeType, _ = mimetypes.guess_type(filePath) + with open(filePath, 'rb') as f: + return self.addUnstructuredData(smartDataContextId, fileName, mimeType, f) + + def addUnstructuredData(self, smartDataContextId, fileName, mimeType, data): + headers = { + 'Content-Type': mimeType, + 'Filename': fileName + } + url = self._url + f"api/v1_1/context.php?action=add-unstructured&smartDataContextId={smartDataContextId}" + response = self._client.post(url, headers=headers, data=data) + try: + response_json = response.json() + self._log_request("POST", url, headers, {"data": "binary data"}, response.json()) + if "errors" in response_json: + raise Exception(f"Error processing request: {json.dumps(response_json['errors'])}") + else: + return response_json['result']['objectId'] + except Exception as e: + raise Exception(f"Invalid response from API: {response.content} - {e}") + + def getUnstructuredData(self, smartDataContextId, objectId): + headers = { + } + url = self._url + "api/v1_1/context.php" + json_request = { + "command": "/unstructured/get", + "request": { + "smartDataContextId": smartDataContextId, + "objectId": objectId + } + } + response = self._client.post(url, headers=headers, json=json_request) + try: + self._log_request("POST", url, dict(response.headers), json_request, "") + response.raise_for_status() + return response.content + except Exception as e: + raise Exception(f"Invalid response from API: {e}") + + def saveUnstructuredDataToFile(self, smartDataContextId, objectId, filePath): + data = self.getUnstructuredData(smartDataContextId, objectId) + with open (filePath, "wb") as f: + f.write(data) + + def removeUnstructuredData(self, smartDataContextId, objectId): + json_request = { + "command": "/unstructured/remove", + "request": { + "smartDataContextId": smartDataContextId, + "objectId": objectId + } + } + + return self._doJsonPost(json_request) \ No newline at end of file diff --git a/docker/smartdatacontext-test/main.py b/docker/smartdatacontext-test/main.py new file mode 100644 index 0000000000000000000000000000000000000000..87f33125cd15f2f0d3e94fa2d6950f9312c63d2a --- /dev/null +++ b/docker/smartdatacontext-test/main.py @@ -0,0 +1,119 @@ +import filecmp +import json +import os + +from client import SmartDataContextAPIClient + +CLIENT_CERTIFICATE="client.pem" +CLIENT_CERTIFICATE_KEY="client.key" +API_URL="https://web/" + +def generate_random_file(filename, size_in_mb): + size_in_bytes = size_in_mb * 1024 * 1024 + with open(filename, 'wb') as f: + f.write(os.urandom(size_in_bytes)) + +# Create a new SmartDataContextClient, passing a custom URL for the API and the certificates to access the domain +# The parameter verifyCertificate allows using self-signed certificates for development and test environments. +# Should not be used in production ideally +client = SmartDataContextAPIClient(cert_file=CLIENT_CERTIFICATE, key_file=CLIENT_CERTIFICATE_KEY, url=API_URL, verifyCertificate=False, + logfile="requests.log") + +# Creates a new perene SmartDataContext (no start not end time). The minimum required parameters are the content +# and the features, with at least one feature (tags), which indicate tags to be associated with this SmartDataContext +# The function returns the create SmartDataContext id or throws an exception in case of error +pereneSmartDataContextId = client.createSmartDataContext(content={"meta": True}, features={"tags": ["sampleTag", "simulation"]}) +print(f"Create a new SmartDataContext with id {pereneSmartDataContextId}") + +# Now that we have a smartDataContext we can associate it to a SmartDataUnit or to SmartDataSource +# A smartDataUnit is represented by its value +# A smartDataSource is represented by its sphere (stationary) or its signature (mobile) +# Either smartDataUnits or smartDataSources or both can be associated to a smartDataContext +# A list of smartDataContextIds could be passed as well +# The function returns the list of updated SmartDataContext object or the single SmartDataContext +smartDataUnits = [0x84963924] +smartDataSources = [ [1,1,2,10], 'aeccd287'] +pereneSmartDataContext = client.associateSmartDataContext(smartDataUnits=smartDataUnits, smartDataSources=smartDataSources, smartDataContextIds=pereneSmartDataContextId) +print(f"Updated SmartDataContext \n {json.dumps(pereneSmartDataContext, indent=2)}") + +# To unassociate just call the unassociateSmartDataContext following the same syntax +smartDataSources = [ [1,1,2,10] ] +pereneSmartDataContext = client.unassociateSmartDataContext(smartDataSources=smartDataSources, smartDataContextIds=pereneSmartDataContextId) +print(f"Updated SmartDataContext \n {json.dumps(pereneSmartDataContext, indent=2)}") + +# To retrieve a SmartDataContext by its id +pereneSmartDataContext = client.getSmartDataContext(smartDataContextId=pereneSmartDataContextId) +print(f"Retrieved SmartDataContext \n {json.dumps(pereneSmartDataContext, indent=2)}") + +# To find all SmartDataContext associated to a set of smartDataSources or smartDataUnits +smartDataContextForSources = client.findSmartDataContext(smartDataSources=smartDataSources) +print(f"Retrieved SmartDataContext for {smartDataSources}: {len(smartDataContextForSources)} \n {json.dumps(smartDataContextForSources, indent=2)}") + +smartDataContextForUnits = client.findSmartDataContext(smartDataUnits=smartDataUnits) +print(f"Retrieved SmartDataContext for {smartDataSources}: {len(smartDataContextForUnits)} \n {json.dumps(smartDataContextForUnits, indent=2)}") + +smartDataContextForUnitsOrSources = client.findSmartDataContext(smartDataUnits=smartDataUnits, smartDataSources=smartDataSources) +print(f"Retrieved SmartDataContext for {smartDataSources} and {smartDataUnits}: {len(smartDataContextForUnitsOrSources)} \n {json.dumps(smartDataContextForUnitsOrSources, indent=2)}") + +# To find all SmartDataContext associated to a set of smartDataSources or smartDataUnits under a time range +temporalSmartDataContextId = client.createSmartDataContext(content={"meta": True}, features={"tags": ["sampleTag", "simulation"]}, + smartDataUnits=smartDataUnits, t0=15, t1=25) +temporalSmartDataContext = client.getSmartDataContext(smartDataContextId=temporalSmartDataContextId) +print(f"Retrieved temporal SmartDataContext \n {json.dumps(temporalSmartDataContext, indent=2)}") + + +smartDataContextForUnitsOrSources = client.findSmartDataContext(smartDataUnits=smartDataUnits, smartDataSources=smartDataSources, t0=16, t1=20) +print(f"Retrieved SmartDataContext for {smartDataSources} and {smartDataUnits} between 16 and 20 : {len(smartDataContextForUnitsOrSources)} \n {json.dumps(smartDataContextForUnitsOrSources, indent=2)}") + + +# To find all SmartDataContext associated to a set of smartDataSources or smartDataUnits under a time range +smartDataContextForUnitsOrSources = client.findSmartDataContext(smartDataUnits=smartDataUnits, smartDataSources=smartDataSources, t0=5, t1=9) +print(f"Retrieved SmartDataContext for {smartDataSources} and {smartDataUnits} between 5 and 9 : {len(smartDataContextForUnitsOrSources)} \n {json.dumps(smartDataContextForUnitsOrSources, indent=2)}") + +# To do a generic query using the MongoDB syntax +query = { + "t0": 15 +} +queryResult = client.querySmartDataContext(query) +print(f"Query result: \n {json.dumps(queryResult, indent=2)}") + +# To change the contents of a SmartDataContext +temporalSmartDataContext = client.updateSmartDataContext(temporalSmartDataContextId, content={"meta": False}) +print(f"Updated temporal SmartDataContext \n {json.dumps(temporalSmartDataContext, indent=2)}") + +# To add an unstructured content to a SmartDataContext +generate_random_file("data.bin", 3) +storageObjectId = client.addUnstructuredDataFromFile(temporalSmartDataContextId, 'data.bin'); +print(f"Stored object id is {storageObjectId}") +temporalSmartDataContext = client.getSmartDataContext(temporalSmartDataContextId); +print(f"Updated temporal SmartDataContext \n {json.dumps(temporalSmartDataContext, indent=2)}") + +generate_random_file("data2.bin", 3) +storageObjectId2 = client.addUnstructuredDataFromFile(temporalSmartDataContextId, 'data2.bin'); +print(f"Stored object id is {storageObjectId}") +temporalSmartDataContext = client.getSmartDataContext(temporalSmartDataContextId); +print(f"Updated temporal SmartDataContext \n {json.dumps(temporalSmartDataContext, indent=2)}") + +# To retrieve an unstructured content from a SmartDataContent +client.saveUnstructuredDataToFile(temporalSmartDataContextId, storageObjectId, "data_retrieved.bin"); +if filecmp.cmp("data.bin", "data_retrieved.bin"): + print("Retrieved file data.bin is identical\n") +else: + raise Exception("Retrieved file data.bin is not identical") +client.saveUnstructuredDataToFile(temporalSmartDataContextId, storageObjectId2, "data2_retrieved.bin"); +if filecmp.cmp("data2.bin", "data2_retrieved.bin"): + print("Retrieved file data2.bin is identical\n") +else: + raise Exception("Retrieved file data2.bin is not identical") + +os.remove("data_retrieved.bin") +os.remove("data2_retrieved.bin") +os.remove("data.bin") +os.remove("data2.bin") + +# To remove a unstructured data +temporalSmartDataContext = client.removeUnstructuredData(temporalSmartDataContextId, storageObjectId) +print(f"Removed unstructured {storageObjectId} - containing unstructured count is {len(temporalSmartDataContext['unstructuredData'])} with id {temporalSmartDataContext['unstructuredData'][0]['id']}") + +temporalSmartDataContext = client.removeUnstructuredData(temporalSmartDataContextId, storageObjectId2) +print(f"Removed unstructured {storageObjectId2} - containing unstructured count is {len(temporalSmartDataContext['unstructuredData'])}") diff --git a/docker/smartdatacontext-test/test-ingress.py b/docker/smartdatacontext-test/test-ingress.py new file mode 100644 index 0000000000000000000000000000000000000000..146e84971d0b7797028713d161dcb573f8468d25 --- /dev/null +++ b/docker/smartdatacontext-test/test-ingress.py @@ -0,0 +1,24 @@ +import filecmp +import json +import os +import sys + +from client import SmartDataContextAPIClient + +CLIENT_CERTIFICATE="client.pem" +CLIENT_CERTIFICATE_KEY="client.key" +API_URL="https://web/" + +# Create a new SmartDataContextClient, passing a custom URL for the API and the certificates to access the domain +# The parameter verifyCertificate allows using self-signed certificates for development and test environments. +# Should not be used in production ideally +client = SmartDataContextAPIClient(cert_file=CLIENT_CERTIFICATE, key_file=CLIENT_CERTIFICATE_KEY, url=API_URL, verifyCertificate=False, + logfile="requests.log") + +# To find all SmartDataContext associated with the vehicle +smartDataContextForSources = client.findSmartDataContext(smartDataSources=['a3edc4456f']) +print(f"Retrieved SmartDataContext for a3edc4456f: {len(smartDataContextForSources)} \n {json.dumps(smartDataContextForSources, indent=2)}") +if len(smartDataContextForSources) != 3: + sys.exit(1) + + diff --git a/docker/smartdatacontext-test/test.key b/docker/smartdatacontext-test/test.key new file mode 100644 index 0000000000000000000000000000000000000000..f76e117aeb7d13cd458c99d62b481679abd20e6a --- /dev/null +++ b/docker/smartdatacontext-test/test.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApsq6IteuqvTf0MuAIuOd3B5CowDpYq9Lu5K3h1dCXCT2bdHU +RK68e6yASt+M9n5FZwYdNakO3CY/KvRLUQkyQnuhnYTaIFtUlDJ/ZyEuskk3iaft +2rQpbzRl8nWK7L+7twrsq2jvEKf3qlPYcDoPdSO9NXIjO+2CrWmpS4u6Q3G8W5Ux +BnFqZNENE6Lh1dV7VY/gN/n/Ii3nyVn4w39428w3tZ4xxi0RxjB2Z5ccp6sTghsQ +POT3GuLx2c53x+/++e5KW/ycTq0dqfcbZgQOLfq8W8J9wVy9d6v1CRzspjKoPLKE +0J+JJFUPYx4xvkreQ+ugZaOO32tDSskKhAgDywIDAQABAoIBAGKZOUvboJ/jPmQZ +yNfXIry7Dr15jn4ODoEGDIRvawU5vIvTHN61RSr/IKAiYxxcLP7oOGA+XlB6M6Fa +5SM0goeWukV6AdBa7TbmWzZGAWrXqYMwyIUfN9dLbHDWV5f5CCj+xkXWNGzuPpRs +GGjPic+/ntx3aqjEkRmeerv0D9pJ76aR1N001y/KV65b8ksbE1nSlhPElfcXVHlH +d+RygfuQMaQkR/PAKEEzCbhAJ8J/Pr8pWLdT8JNdV77keqCwyRXIyatYGLGPlpmz +o7V+zPqnCIUzOL90VVZcFCyqAZPc4urW8ZGAIdOmEFPN+MrkxuTMDbKwmq+t+6h8 +/t6kx6ECgYEA01CLOajY+5Ftvpaw4I2A7j/Tiy/bYr5y9dekuhLZVYRk2XWZuQaz ++SytAu0OtfqHdz/4wR1aqqtTWa3V5PiKlB+5yB9mhTLdBpgG+/tQ6HdZ9/j1JffO +9MBXibT6/5Ht4hC43ghe58aIzkTXdY4735C9QlyAVPRL05YPxCZaYjkCgYEAyg/1 +ZK2vIoBUri2Fl8Zzgydui60POstVDjH2eDEaLoIWz5krroxu/HD/wFGrwLksAd7i +TnwBc4uhpyFbe4DVRqam2p83UhsBPOyg8ETmVA0O4p8sPqWPfzFoCnrGGi9bjX3i ++ISahwrUswZQe/sAKxt1nAncyoJAmUd6n7FZRiMCgYBsGl1PPxDaCbkB8mdEST+3 +IpHfN7ldF58KI6qSwofKlvNcyCu/Gy3BAFj2ET4gXK0mlWR1rsWuX/1A+IxfGcMT +KfPJ7D5vT+kAsY+JIzVt53jwQb1wEgke7rMZsKtjSU1QBEv+0ntyyP89vrwW2I/K +vGoF4/qG+387HB6usww0CQKBgAXWIitvTOC9PjTtwa8Zn1qJeAr2y7rSBrnrPYhJ +Os1fGpOShA19pvMJvIlskn51mszPCFBmCpCw1tGCCCdwGDj7NVSZRrrLZuTAnQFz +rAd/fQ+Us2130MZGwg+TbkdxWL6khTrScbg22t/07bw3CtEz8Ni5/DOpQX5r7yHP +uJlrAoGBAJ54FrubIqitYAM+4bnv1gABKBNQnrqaQMWN6N2nxyICu7VH7nxqpr0s +41lM/ecwEoNm7hRP1PRKqq9/HULV/0IgduhkOo1koRl5c9FsLDoK+GHA+qbdwF5D +yDKoH/IJ3oUj8gaQPYwsxmAvX+My7gVGHs3ezjAzkAp5VQa5CPwo +-----END RSA PRIVATE KEY----- diff --git a/docker/smartdatacontext-test/test.pem b/docker/smartdatacontext-test/test.pem new file mode 100644 index 0000000000000000000000000000000000000000..a1fe892d256e80460a20f7523a1089ae039fd94f --- /dev/null +++ b/docker/smartdatacontext-test/test.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMTCCAhkCFHYQTtvmyKXMa7sovZ5/68rBojgVMA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwOTA1MTgxNTU4WhcNMjgxMDA1MTgx +NTU4WjBlMQswCQYDVQQGEwJCUjELMAkGA1UECAwCU0MxFjAUBgNVBAcMDUZsb3Jp +YW5vcG9saXMxDTALBgNVBAoMBFVGU0MxDjAMBgNVBAsMBUxpc2hhMRIwEAYDVQQD +DAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmyroi +166q9N/Qy4Ai453cHkKjAOlir0u7kreHV0JcJPZt0dRErrx7rIBK34z2fkVnBh01 +qQ7cJj8q9EtRCTJCe6GdhNogW1SUMn9nIS6ySTeJp+3atClvNGXydYrsv7u3Cuyr +aO8Qp/eqU9hwOg91I701ciM77YKtaalLi7pDcbxblTEGcWpk0Q0TouHV1XtVj+A3 ++f8iLefJWfjDf3jbzDe1njHGLRHGMHZnlxynqxOCGxA85Pca4vHZznfH7/757kpb +/JxOrR2p9xtmBA4t+rxbwn3BXL13q/UJHOymMqg8soTQn4kkVQ9jHjG+St5D66Bl +o47fa0NKyQqECAPLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEjKM4VVLY7xC+qj +s8W5W4dBx41fQm7Y1NNCzAZiag4SoC8eSLkiZ57TtNARmtT/jsdr0RlCusFQdhsJ +k42gHMXdL4vY1pcGp0OXXV5fDc9xPY74CoeLItuJHxwdNTwpxWsbXTu8bCl76SHF +9hgx1QToemFLPQqaE2E21SeF0+WVBpVzqaUCJjl+B5zzwfrAz9RHKwIGnf8EZEe2 +2gfxsm1s29uT7KAOgOjwqZl8uzHCepHAbSKr5YcbbxjpNRzMWA6aeCnJ/4hoD3Rm +USDg885dgyTNlrhcAQV11yJMeXeQd6h/yOTkHCJlstpJfkcIhXsqRZUDgL69U/Z4 +hwPfGdU= +-----END CERTIFICATE-----