Commit 0c6efabf authored by Muhammad Umair Khan's avatar Muhammad Umair Khan
Browse files

chore: merge STF_707_TASK3 and resolve tinyIoT submodule conflicts

parents e16b2d57 964fe4e4
Loading
Loading
Loading
Loading
Compare 14e4658a to 8e86af28
Original line number Diff line number Diff line
Subproject commit 14e4658a805a66b5dd2528c77806f3199a500690
Subproject commit 8e86af28269336ea0552508884d12203fc70cddf
+1 −1
Original line number Diff line number Diff line
Demo9 is a MEC application to illustrate the joint usage of the ETSI MEC platform and oneM2M platform.
Demo9 is a MEC application to illustrate the joint usage of the ETSI MEC platform and oneM2M CSE.

# How to use it

+1 −1
Original line number Diff line number Diff line
@@ -111,4 +111,4 @@ workdir="/usr/src/app/ACME-oneM2M-CSE"
cd "$workdir" || { echo "Directory $workdir not found"; exit 1; }
envsubst < acme.ini.in > acme.ini
cat acme.ini
python3 -m acme
python3 -m acmecse
+1 −1
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ RUN echo "meep-acme-mn-cse" > /etc/hostname \

WORKDIR /usr/src/app

RUN git clone --branch development https://github.com/ankraft/ACME-oneM2M-CSE.git ACME-oneM2M-CSE
RUN git clone https://github.com/ankraft/ACME-oneM2M-CSE.git ACME-oneM2M-CSE

WORKDIR /usr/src/app/ACME-oneM2M-CSE

+0 −388
Original line number Diff line number Diff line
#
#	MECClient.py
#
#	(c) 2021 by Andreas Kraft
#	License: BSD 3-Clause License. See the LICENSE file for further details.
#
"""	Implementation of an MEC Client for an MEC Mcx binding implementation.
"""

from __future__ import annotations
from typing import Tuple, cast, Dict, Optional, Any

import time
import flask
from flask import Flask, Request, request

from werkzeug.wrappers import Response
from werkzeug.serving import WSGIRequestHandler
from werkzeug.datastructures import MultiDict as WerkzeugMultiDict
from waitress import serve
from flask_cors import CORS
import requests
import isodate

from urllib.parse import urlparse, unquote

from ..etc.Types import Result, ResponseStatusCode
from ..runtime.Configuration import Configuration

from ..runtime.Logging import Logging as L

import uuid

import json

class MECClient(object):
	"""	The general MEC manager for this CSE.
	"""
	# TODO doc

	__slots__ = (
		'http_address',
		'http_port',
		'cse_external_ip',
		'cse_external_port',
		'use_wss',
		'mqtt_address',
		'mqtt_port',
		'wss_path',
		'cse_resourceID',
		'fullAddress',
		'isStopped',
		'mec_app_instance_id',
		'mecConnection',
		'mecConnections',
		'isInfo',
		'headers',
		'register'
	)
	""" Slots for the MECClient. """

	# TODO move config handling to event handler

	def __init__(self, p_http_address: str, p_http_port: str, p_cse_resourceID: str, 
			  p_cse_external_ip: str, p_cse_external_port: str, p_use_wss: bool, p_mqtt_address: str, 
			  p_mqtt_port: str, p_fullAddress: str, p_mec_app_instance_id: str , p_wss_path: str = "") -> None:
		"""	Initialize the MEC client.
		"""
		self.http_address = p_http_address
		u = urlparse(self.http_address)
		self.http_address = u.hostname
		self.http_port = p_http_port
		self.cse_external_ip = p_cse_external_ip
		self.cse_external_port = p_cse_external_port
		self.cse_resourceID = p_cse_resourceID
		self.use_wss = p_use_wss
		self.mqtt_address = p_mqtt_address
		self.mqtt_port = p_mqtt_port
		self.wss_path = p_wss_path
		self.fullAddress = p_fullAddress
		self.mec_app_instance_id = p_mec_app_instance_id

		self.isStopped = False
		""" Flag to indicate whether the MEC client is stopped. """

		self.mecConnections:Dict[Tuple[str, int], Flask]	= {}
		""" Dictionary of MEC connections. """

		self.mecConnection = self.connectToMec(host = Configuration.mec_host, port = Configuration.mec_port)
		""" The MEC connection. """

		self.headers = {}
		self.headers['Content-Type'] = 'application/json; charset=UTF-8'

		self.register = None

		L.isInfo and L.log('MEC Client initialized')
		L.isInfo and L.log('MEC Client: self.http_address=' + str(self.http_address))

	def run(self) -> bool:
		"""	Initialize and run the MEC client as a BackgroundWorker/Actor.
		"""
		if not Configuration.mec_enable or not self.mecConnection:
			L.isInfo and L.log('MEC: client NOT enabled')
			return True
		L.isInfo and L.log('Start MEC client')
		self.mecConnection.run()
		#if not self.isFullySubscribed():	# This waits until the MEC Client connects and fully subscribes (until a timeout)
		#	return False
		return True


	def shutdown(self) -> bool:
		"""	Shutdown the MECClient.
		"""
		for id in list(self.mecConnections):
			self.disconnectFromMec(id[0], id[1])	# 0 = host, 1 = port
		self.mecConnection = None
		L.isInfo and L.log('MEC client shut down')
		return True
	

	def configUpdate(self, name:str, 
						   key:Optional[str] = None, 
						   value:Optional[Any] = None) -> None:
		"""	Callback for the `configUpdate` event.
			
			Args:
				name: Event name.
				key: Name of the updated configuration setting.
				value: New value for the config setting.
		"""
		if key not in [ 'mec.host',
						'mec.enable',
						'mec.port',
						'mec.protocol',
						'mec.mec_platform',
						'mec.mec_sandbox_id'
					  ]:
			return

		# possibly restart MEC client
		self.shutdown()
		self.run()
		

	def pause(self) -> None:
		"""	Stop handling requests.
		"""
		L.isInfo and L.log('MecClient paused')
		self.isStopped = True
		
	
	def unpause(self) -> None:
		"""	Continue handling requests.
		"""
		L.isInfo and L.log('MecClient unpaused')
		self.deregisterFromMec()
		L.isInfo and L.log('MecClient deregistering done')
		self.registerToMec()
		L.isInfo and L.log('MecClient re-registering done')
		self.isStopped = False


	#
	#	Additional methods
	#

	def connectToMec(self, host:str, port:int) -> Optional[Flask]:
		if Configuration.mec_enable:
			if not (mecConnect := self.mecConnections.get( (host, port) )):
				mecConnection = Flask(host)
				if mecConnection:
					self.mecConnections[(host, port)] = mecConnection
			return mecConnection
		return None

	
	def disconnectFromMec(self, host:str, port:int) -> None:
		"""	Remove the appropriate MECConnection for *host* and *port* from the 
			connection cache and also shut-down the connection.
		"""
		if (mecConnection := self.getMec(host, port)):
			if self.register is not None:
				self.deregisterFromMec()
			del self.mecConnections[ (host, port) ]
			# FIXME FSCOM Terminate Flask listener thread
	

	def getMec(self, host:str, port:int) -> Flask:
		"""	Return the MECConnection for the *host* and *port* from the internal
			connection cache.
		"""
		return self.mecConnections.get( (host, port) )


	#########################################################################
	#
	#	Send MEC requests
	#

	def registerToMec(self) -> Result:
		"""	Register to MEC platform.
		"""

		if self.isStopped:
			return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, 
						  dbg = 'MEC client is not running')

		try:
			url = \
				Configuration.mec_protocol + '://' + Configuration.mec_host + ':' + str(Configuration.mec_port) + \
				'/' + Configuration.mec_sandbox_id + \
				'/' + Configuration.mec_platform + \
				'/iots/v1/registered_iot_platforms'
			L.isInfo and L.log('MEC Client: registerToMec=' + url)
			body_json = {}
			body_json['iotPlatformId'] = str(uuid.uuid4())
			userTransportInfoList = []
			userTransportInfo = {}
			userTransportInfo['id'] = str(uuid.uuid4())
			userTransportInfo['name'] = Configuration.cse_cseID
			userTransportInfo['type'] = 'MB_TOPIC_BASED'
			if self.use_wss:
				userTransportInfo['description'] = 'MQTT over WS-Secured'
				userTransportInfo['protocol'] = 'MQTT+WSS'
			else:
				userTransportInfo['description'] = 'MQTT'
				userTransportInfo['protocol'] = 'MQTT'
			userTransportInfo['version'] = '2'
			userTransportInfo['endpoint'] = {}
			# userTransportInfo['endpoint']['addresses'] = []
			# The MEC API expects an array of URIs for the endpoint 'uris' field.
			# Previously this was set to a single string which caused the server
			# to return: "cannot unmarshal string into Go struct field ... uris of type []string".
			if self.use_wss:
				userTransportInfo['endpoint']['uris'] = []
				userTransportInfo['endpoint']['uris'].append('wss://' + self.mqtt_address + ':' + str(self.mqtt_port) + self.wss_path)
			else:
				userTransportInfo['endpoint']['addresses'] = []
				address = {}
				address['host'] = self.mqtt_address
				address['port'] = self.mqtt_port
				userTransportInfo['endpoint']['addresses'].append(address)
				# userTransportInfo['endpoint']['uris'] = [ self.fullAddress ]
			# address = {}
			# address['host'] = self.mqtt_address
			# address['port'] = self.mqtt_port
			# userTransportInfo['endpoint']['addresses'].append(address)
			userTransportInfo['security'] = {}
			userTransportInfo['implSpecificInfo'] = {}
			userTransportInfoList.append(userTransportInfo)
			body_json['userTransportInfo'] = userTransportInfoList
			customServicesTransportInfoList = []
			customServicesTransportInfo = {}
			# customServicesTransportInfo['id'] = str(uuid.uuid4())
			customServicesTransportInfo['id'] = Configuration.cse_cseID
			customServicesTransportInfo['name'] = Configuration.cse_resourceName
			customServicesTransportInfo['description'] = 'ACME oneM2M CSE ID'
			customServicesTransportInfo['type'] = 'REST_HTTP'
			customServicesTransportInfo['protocol'] = 'REST_HTTP'
			customServicesTransportInfo['version'] = '4'
			customServicesTransportInfo['endpoint'] = {}
			if self.fullAddress == '':
				customServicesTransportInfo['endpoint']['uris'] = [ 'https://' + self.cse_external_ip + ':' + str(self.http_port) ]
			else:
				customServicesTransportInfo['endpoint']['uris'] = [ self.fullAddress ]
			customServicesTransportInfo['security'] = {}
			customServicesTransportInfoList.append(customServicesTransportInfo)
			body_json['customServicesTransportInfo'] = customServicesTransportInfoList
			body_json['enabled'] = True
			L.isInfo and L.log('MEC Client: body_json=' + str(body_json))

			tries = 0
			while tries < 5:
				response = requests.post(url, headers=self.headers, json=body_json, verify=False)
				L.isInfo and L.log(f'MECClient.registerToMec: response.content: ' + str(response.content))
				if response.status_code == 201:
					self.register = Result()
					self.register.rsc = response.status_code
					self.register.data = response.content
					return self.register
				else:
					L.logErr(f'MECClient.registerToMec: ' + str(response.status_code))
					tries += 1 # Continue the loop
					time.sleep(10)
		except Exception as e:
			L.logErr(f'MECClient.registerToMec: ' + str(e))

		return None

	def deregisterFromMec(self) -> Result:
		"""	Deregister to MEC platform.
		"""
		if self.isStopped:
			return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC client is not running')

		if self.register is not None:
			try:
				json_dict = json.loads(self.register.data.decode('utf-8'))
				url = \
					Configuration.mec_protocol + '://' + Configuration.mec_host + ':' + str(Configuration.mec_port) + \
					'/' + Configuration.mec_sandbox_id + \
					'/' + Configuration.mec_platform + \
					'/iots/v1/registered_iot_platforms/' + json_dict['iotPlatformId']
				response = requests.delete(url, headers=self.headers, verify=False)
				self.register = None
				return Result(rsc = response.status_code)
			except Exception as e:
				L.logErr(f'MECClient.deregisterToMec: ' + str(e))
			
		self.register = None
		return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC Deregistration failure')

	def confirm_readyToMec(self) -> Result:
		"""	Confirm readiness to MEC platform.
		"""
		if self.isStopped:
			return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC client is not running')

		try:
			req_body = {
				"indication": "READY"
			}
			url = \
				Configuration.mec_protocol + '://' +Configuration.mec_host + ':' + str(Configuration.mec_port) + \
				'/' + Configuration.mec_sandbox_id + \
				'/' + Configuration.mec_platform + \
				'/mec_app_support/v2/applications/' + self.mec_app_instance_id + '/confirm_ready'
			L.log('MECClient.confirm_readyToMec: ' + str(req_body))
			L.log('MECClient.confirm_readyToMec: ' + str(url))
			response = requests.post(url, headers=self.headers, json=req_body, verify=False)
			return Result(rsc = response.status_code)
		except Exception as e:
			L.logErr(f'MECClient.confirm_readyToMec: ' + str(e))
			return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC Confirm readiness failure')

	def post_mec_service(self) -> Result:
		"""
			Declare MEC service to MEC platform.
			This function declares MN-CSE as MEC service to the MEC platform.
		"""
		if self.isStopped:
			return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC client is not running')

		try:
			req_body = {
				"serName": Configuration.cse_resourceName,
				"serCategory": {
					"href": "catalogueHref",
					"id": Configuration.cse_cseID,
					"name": Configuration.cse_resourceName,
					"version": "v1"
				},
				"version": "v1.0.0",
				"state": "ACTIVE",
				"transportInfo": {
					"id": "acme-mn-cse-transport",
					"name": "REST",
					"type": "REST_HTTP",
					"protocol": "HTTPS",
					"version": "4.0",
					"endpoint": {
						"uris": [
							self.fullAddress
						],
						"fqdn": None,
						"addresses": None,
						"alternative": None
					},
					"security": None
				},
				"serializer": "JSON",
				"scopeOfLocality": "MEC_SYSTEM",
				"consumedLocalOnly": True
			}
			url = \
				Configuration.mec_protocol + '://' + Configuration.mec_host + ':' + str(Configuration.mec_port) + \
				'/' + Configuration.mec_sandbox_id + \
				'/' + Configuration.mec_platform + \
				'/mec_service_mgmt/v1/applications/' + self.mec_app_instance_id + '/services'
			L.log('MECClient.POST_mec_service: ' + str(req_body))
			L.log('MECClient.POST_mec_service: ' + str(url))
			response = requests.post(url, headers=self.headers, json=req_body, verify=False)
			return Result(rsc = response.status_code)
		except Exception as e:
			L.logErr(f'MECClient.POST_mec_service: ' + str(e))
			return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC Declare MEC service failure')
 No newline at end of file
Loading