cmx.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #
  2. # Copyright (c) 2017-2018 Lionel Hercot <lhercot@cisco.com>
  3. # All rights reserved.
  4. #
  5. import requests
  6. import sys
  7. import json
  8. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  9. from flask import Flask
  10. from flask import abort
  11. from flask_restful import Api
  12. from flask_restful import Resource
  13. from flask_restful import reqparse
  14. from flask import send_file
  15. from pathlib import Path
  16. from io import StringIO
  17. from io import BytesIO
  18. from ipaddress import IPv6Address
  19. import os
  20. import traceback
  21. import CLEUCreds
  22. from cleu.config import Config as C
  23. from PIL import Image, ImageDraw
  24. app = Flask(__name__)
  25. api = Api(app)
  26. # Disable SSL warnings
  27. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  28. @app.after_request
  29. def after_request(response):
  30. response.headers.add("Access-Control-Allow-Origin", "*")
  31. response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
  32. response.headers.add("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE")
  33. return response
  34. cmxBaseUrl = C.CMX
  35. cmxAuth = (CLEUCreds.CMX_USERNAME, CLEUCreds.CMX_PASSWORD)
  36. PORT = 8002
  37. imgDir = "maps/"
  38. markerDir = "markers/"
  39. markerFiles = os.listdir(markerDir)
  40. markers = {}
  41. for filename in markerFiles:
  42. name = filename.split(".")[0]
  43. markers[name] = Image.open(markerDir + filename).convert("RGBA")
  44. markers[name].thumbnail((100, 100), Image.ANTIALIAS)
  45. # markerW, markerH = marker.size
  46. apiUri = {
  47. "mac": "/api/location/v2/clients?macAddress=",
  48. "ip": "/api/location/v2/clients?ipAddress=",
  49. "map": "/api/config/v1/maps/imagesource/",
  50. "ssid": "/api/location/v2/clients/?include=metadata&pageSize=1000&page=",
  51. "count": "/api/location/v2/clients/count",
  52. "floors": "/api/config/v1/maps",
  53. "tag": "/api/location/v1/tags/",
  54. }
  55. def serve_pil_image(pil_img):
  56. img_io = BytesIO()
  57. pil_img.save(img_io, "JPEG", quality=100)
  58. img_io.seek(0)
  59. return send_file(img_io, mimetype="image/jpeg")
  60. def getMap(imageName):
  61. imgFile = Path(imgDir + imageName)
  62. if not imgFile.is_file():
  63. respImg = requests.get(cmxBaseUrl + apiUri["map"] + imageName, auth=cmxAuth, verify=False, stream=True)
  64. if respImg.status_code == 200:
  65. with open(str(imgFile), "wb") as f:
  66. for chunk in respImg:
  67. f.write(chunk)
  68. def getAllFloorMaps():
  69. response = requests.get(cmxBaseUrl + apiUri["floors"], auth=cmxAuth, verify=False)
  70. floorList = {}
  71. if response and response != "":
  72. try:
  73. dataDict = json.loads(response.text)
  74. if "campuses" in dataDict:
  75. for campus in dataDict["campuses"]:
  76. if "buildingList" in campus:
  77. for building in campus["buildingList"]:
  78. for floor in building["floorList"]:
  79. floorId = floor["aesUid"]
  80. imageName = floor["image"]["imageName"]
  81. floorList[floorId] = imageName
  82. getMap(imageName)
  83. except:
  84. print("Unexpected error" + str(sys.exc_info()[0]), file=sys.stderr)
  85. raise
  86. return floorList
  87. class CMX(Resource):
  88. def get(self):
  89. parser = reqparse.RequestParser()
  90. parser.add_argument("ip", help="IP address of the endpoint")
  91. parser.add_argument("ipv6", help="IPv6 address of the endpoint")
  92. parser.add_argument("mac", help="MAC address of the endpoint")
  93. parser.add_argument("marker", help="Marker used to display the location of the endpoint", default="marker")
  94. parser.add_argument("size", help="Size of the image returned")
  95. parser.add_argument("tag", help="Asset tag MAC address")
  96. args = parser.parse_args()
  97. response = ""
  98. if args.get("ip"):
  99. clientIp = args.get("ip")
  100. response = requests.get(cmxBaseUrl + apiUri["ip"] + clientIp, auth=cmxAuth, verify=False)
  101. elif args.get("tag"):
  102. clientMac = args.get("tag")
  103. response = requests.get(cmxBaseUrl + apiUri["tag"] + clientMac, auth=cmxAuth, verify=False)
  104. elif args.get("mac"):
  105. clientMac = args.get("mac")
  106. response = requests.get(cmxBaseUrl + apiUri["mac"] + clientMac, auth=cmxAuth, verify=False)
  107. elif args.get("ipv6"):
  108. clientIp = IPv6Address(args.get("ipv6")).exploded
  109. response = requests.get(cmxBaseUrl + apiUri["ip"] + clientIp, auth=cmxAuth, verify=False)
  110. markerName = args.get("marker")
  111. marker = markers["marker"]
  112. if markerName in markers:
  113. marker = markers[markerName]
  114. markerW, markerH = marker.size
  115. if response and response != "":
  116. try:
  117. dataDict = json.loads(response.text)
  118. result = None
  119. if args.get("tag") and dataDict and "mapInfo" in dataDict:
  120. result = dataDict
  121. elif len(dataDict) > 0 and "mapInfo" in dataDict[0]:
  122. result = dataDict[0]
  123. if result is not None:
  124. imageName = result["mapInfo"]["image"]["imageName"]
  125. mapLength = result["mapInfo"]["floorDimension"]["length"]
  126. mapWidth = result["mapInfo"]["floorDimension"]["width"]
  127. imageLength = result["mapInfo"]["image"]["height"]
  128. imageWidth = result["mapInfo"]["image"]["width"]
  129. coordX = result["mapCoordinate"]["x"]
  130. coordY = result["mapCoordinate"]["y"]
  131. positionX = (imageWidth / mapWidth) * coordX
  132. positionY = (imageLength / mapLength) * coordY
  133. getMap(imageName)
  134. im = Image.open(str(imgDir + imageName))
  135. positionX = positionX - markerW / 2
  136. positionY = positionY - markerH
  137. offset = (int(positionX), int(positionY))
  138. im.paste(marker, offset, marker)
  139. if args.get("size"):
  140. # print('SIZE', file=sys.stderr)
  141. size = args.get("size")
  142. im.thumbnail((int(size), int(size)), Image.ANTIALIAS)
  143. return serve_pil_image(im)
  144. else:
  145. abort(404, "Requested element not found")
  146. except Exception as inst:
  147. print("Unexpected error with request= {} | error : {}".format(response.text, inst), file=sys.stderr)
  148. return {"response": str(response.text), "error": str(inst)}, 500
  149. return abort(404, "Missing parameter ip, ipv6 or mac. Other possible parameters are: marker (" + ", ".join(markers.keys()) + ")")
  150. class CMX_SSID(Resource):
  151. def get(self):
  152. parser = reqparse.RequestParser()
  153. parser.add_argument("ssid", help="SSID used by the clients")
  154. parser.add_argument("floor", help="Floor used by the clients")
  155. parser.add_argument("marker", help="Marker used to display the location of the endpoint", default="marker")
  156. args = parser.parse_args()
  157. response = ""
  158. if args.get("ssid"):
  159. ssid = args.get("ssid")
  160. countResp = requests.get(cmxBaseUrl + apiUri["count"], auth=cmxAuth, verify=False)
  161. try:
  162. dataDict = json.loads(countResp.text)
  163. if "count" in dataDict:
  164. count = dataDict["count"]
  165. maxPageId = (count // 1000) + 1
  166. print("Count: {} MaxPage: {}".format(count, maxPageId), file=sys.stderr)
  167. userList = {}
  168. floorList = getAllFloorMaps()
  169. for pageId in range(1, maxPageId):
  170. print("Page: {} MaxPage: {}".format(pageId, maxPageId), file=sys.stderr)
  171. response = requests.get(cmxBaseUrl + apiUri["ssid"] + str(pageId), auth=cmxAuth, verify=False)
  172. if response and response.text != "":
  173. try:
  174. userDict = json.loads(response.text)
  175. for user in userDict:
  176. if user["ssId"] == ssid:
  177. floorName = user["mapInfo"]["floorRefId"]
  178. if floorName in userList:
  179. userList[floorName].append(user)
  180. else:
  181. userList[floorName] = [user]
  182. except:
  183. print(
  184. "Unexpected error with page request= " + response.text + " | error : " + str(sys.exc_info()[0]),
  185. file=sys.stderr,
  186. )
  187. return {"response": str(response.text), "error": str(inst)}, 500
  188. if args.get("floor"):
  189. floor = args.get("floor")
  190. if floor in userList:
  191. markerName = args.get("marker")
  192. marker = ""
  193. if markerName in markers:
  194. marker = markers[markerName]
  195. else:
  196. marker = markers["marker"]
  197. markerW, markerH = marker.size
  198. imageName = floorList[floorName]
  199. getMap()
  200. im = Image.open(str(imgDir + imageName))
  201. for data in userList[floor]:
  202. mapInfo = data["mapInfo"]
  203. mapLength = mapInfo["floorDimension"]["length"]
  204. mapWidth = mapInfo["floorDimension"]["width"]
  205. imageLength = mapInfo["image"]["height"]
  206. imageWidth = mapInfo["image"]["width"]
  207. coordX = data["mapCoordinate"]["x"]
  208. coordY = data["mapCoordinate"]["y"]
  209. positionX = (imageWidth / mapWidth) * coordX
  210. positionY = (imageLength / mapLength) * coordY
  211. positionX = positionX - markerW / 2
  212. positionY = positionY - markerH
  213. offset = (int(positionX), int(positionY))
  214. im.paste(marker, offset, marker)
  215. return serve_pil_image(im)
  216. elif floor in floorList:
  217. imageName = floorList[floor]
  218. getMap(imageName)
  219. im = Image.open(str(imgDir + imageName))
  220. return serve_pil_image(im)
  221. else:
  222. abort(404)
  223. else:
  224. return list(floorList.keys())
  225. except Exception as inst:
  226. print("Unexpected error with request= {} | error : {}".format(countResp, inst), file=sys.stderr)
  227. return {"response": str(countResp), "error": str(inst)}, 500
  228. class sync(Resource):
  229. def get(self):
  230. return getAllFloorMaps()
  231. class home(Resource):
  232. def get(self):
  233. return {}
  234. api.add_resource(home, "/")
  235. api.add_resource(CMX, "/api/v0.1/cmx")
  236. api.add_resource(CMX_SSID, "/api/v0.1/ssid")
  237. api.add_resource(sync, "/api/v0.1/sync")
  238. if __name__ == "__main__":
  239. app.run(host=C.WSGI_SERVER, debug=True, port=PORT, threaded=True)