Commit fb546f33 authored by Yann Garcia's avatar Yann Garcia
Browse files

Add MECSupport plugin in latest ACME release

parent 2bd0f37f
Loading
Loading
Loading
Loading
+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

+7 −2
Original line number Diff line number Diff line
@@ -44,8 +44,8 @@ allowedCSROriginators=/*

[cse.registrar]
INCSEcseID=/$REMOTE_CSE_ID
address=https://${basic.config:registrarCseHost}:${basic.config:registrarCsePort}/
root=$REMOTE_SVC_PATH
;address=https://${basic.config:registrarCseHost}:${basic.config:registrarCsePort}/
;root=$REMOTE_SVC_PATH

[textui]
startWithTUI=false
@@ -63,6 +63,11 @@ externalRoot=/$SVC_PATH/
[webui]
root=/webui

#[http.wsgi]
#enable=true
#threadPoolSize=100
#connectionLimit=100 

[mqtt]
enable=$MQTT_ENABLE
address=$MQTT_HOST
+0 −43
Original line number Diff line number Diff line
// Created by SDTTool
// oneM2M Release: 4.12.0
// Domain:         org.onem2m.common
[

    // MecDeviceClass: iotDevice
    {
        "type"      : "mec:iotDev",
        "lname"     : "iotDevice",
        "sdttype"   : "mecdevice",
        "cnd"       : "org.onem2m.common.mec.device.iotDevice",
        "attributes": [
            // Property: deviceId
            { "sname" : "devId", "lname" : "deviceId", "type" : "string", "car" : "01", "oc" : "O" },  //0 Optional, 1..N, cardinality, oc: OnCreate Mandatory, Update, Optional, Np: Not provisioned
            // Property: gspi
            { "sname" : "gspi", "lname" : "gspi", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: pei
            { "sname" : "mpei", "lname" : "mec-pei", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: supi
            { "sname" : "supi", "lname" : "supi", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: msisdn
            { "sname" : "msisdn", "lname" : "msisdn", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: imei
            { "sname" : "imei", "lname" : "imei", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: imsi
            { "sname" : "imsi", "lname" : "imsi", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: iccid
            { "sname" : "iccid", "lname" : "iccid", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: requestedIotPlatformId
            { "sname" : "rIPId", "lname" : "requestedIotPlatformId", "type" : "string", "car" : "01" },
            // Property: requestedUserTransportId
            { "sname" : "rUTId", "lname" : "requestedUserTransportId", "type" : "string", "car" : "01", "oc" : "O" },
            // Property: requestedMecTrafficRule
            { "sname" : "rMTRe", "lname" : "requestedMecTrafficRule",  "type" : "list", "car" : "01", "oc" : "O" },
            // Property: enabled
            { "sname" : "enabd",  "lname" : "enabled",     "type" : "boolean",   "car" : "1",  "oc" : "M" }
        ],
        "children"  : [
            // Module class
        ]
    }

]
+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