Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | """ |
| 3 | Copyright 2017-present Open Networking Laboratory |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | """ |
| 16 | |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 17 | import argparse |
| 18 | import json |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 19 | import subprocess |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 20 | import sys |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 21 | import os |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 22 | from httplib import OK, NOT_FOUND, BAD_REQUEST |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 23 | from subprocess import Popen |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 24 | |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 25 | from flask import Flask, jsonify, request |
| 26 | from jsonschema import validate |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 27 | |
| 28 | """ |
| 29 | Onos Distributed Manager |
| 30 | this app handles controller information in system |
| 31 | |
| 32 | Message Samples : |
| 33 | --Adding node |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 34 | curl -H "Content-Type: application/json" -X POST --data '{"id":"node-1","ip":"1.2.3.4","port":3456}' http://localhost:5000 |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 35 | --Updating node |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 36 | curl -H "Content-Type: application/json" -X PUT --data '{"id":"node-1","ip":"1.2.3.4","port":3456}' http://localhost:5000 |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 37 | --Deleting node |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 38 | curl -H "Content-Type: application/json" -X DELETE --data '{"id":"node-1","ip":"1.2.3.4","port":3456}' http://localhost:5000 |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 39 | --Getting node data |
| 40 | curl -X GET http://10.15.176.228:5000/cluster.json |
| 41 | |
| 42 | """ |
| 43 | |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 44 | |
| 45 | schema = { |
| 46 | "type": "object", |
| 47 | "properties": { |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 48 | "id": {"type": "string"}, |
| 49 | "port": {"type": "number"}, |
| 50 | "ip": {"type": "string"}, |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 51 | }, |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 52 | "required": ["id", "port", "ip"] |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 53 | } |
| 54 | |
| 55 | |
| 56 | def is_valid_node_json(content): |
| 57 | try: |
| 58 | validate(content, schema) |
| 59 | except Exception as validation_error: |
| 60 | return False |
| 61 | |
| 62 | return True |
| 63 | |
| 64 | class Manager: |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 65 | cluster_config = { |
| 66 | "nodes": [], |
| 67 | "name": 315256287, |
| 68 | "partitions": [] |
| 69 | } |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 70 | persistence_filename = "" |
| 71 | |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 72 | def found_node_index(self, content): |
| 73 | for index, node in enumerate(self.cluster_config["nodes"]): |
| 74 | if node["id"] == content["id"]: |
| 75 | return index |
| 76 | return None |
| 77 | |
| 78 | |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 79 | def save_to_file(self): |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 80 | config = self.cluster_config |
| 81 | if len(self.cluster_config["nodes"]) > 0: |
| 82 | command = ["/Users/ray/Documents/work/onos-next/tools/test/bin/onos-gen-partitions", "" ] |
| 83 | for controller_node in self.cluster_config["nodes"]: |
| 84 | node_string = controller_node["id"] |
| 85 | node_string += ":" |
| 86 | node_string += controller_node["ip"] |
| 87 | node_string += ":" |
| 88 | node_string += str(controller_node["port"]) |
| 89 | command.append(node_string) |
| 90 | process = Popen(command, stdout=subprocess.PIPE) |
| 91 | config = json.load(process.stdout) |
| 92 | self.cluster_config = config |
| 93 | else: |
| 94 | self.cluster_config["nodes"] = [] |
| 95 | self.cluster_config["partitions"] = [] |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 96 | try: |
| 97 | with open(self.persistence_filename, 'w') as file: |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 98 | file.write(json.dumps(config)) |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 99 | file.close() |
| 100 | except Exception as error: |
| 101 | print error |
| 102 | sys.exit() |
| 103 | |
| 104 | def load_from_file(self): |
| 105 | try: |
| 106 | with open(self.persistence_filename, 'r') as file: |
| 107 | data = file.read() |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 108 | self.cluster_config = json.loads(data) |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 109 | except Exception: |
| 110 | self.save_to_file() |
| 111 | |
| 112 | def data_get_handler(self): |
| 113 | |
| 114 | pagereturn = "<h2> Onos Distributed Controller Manager2 </h2>" |
| 115 | pagereturn += "<br><h3> Status of Added controllers </h3><br>" |
| 116 | pagereturn += " Id,  Ip,  Port,  Is Active <br> " |
| 117 | |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 118 | for key in self.cluster_config["nodes"].keys(): |
| 119 | pagereturn += self.cluster_config["nodes"][key]["id"] + ",  " + \ |
| 120 | self.cluster_config["nodes"][key]["ip"] + ",  " + \ |
| 121 | str(self.cluster_config["nodes"][key]["port"]) + ",  " |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 122 | pagereturn += " <br>" |
| 123 | |
| 124 | return pagereturn, OK |
| 125 | |
| 126 | def data_post_handler(self, content): |
| 127 | if not is_valid_node_json(content): |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 128 | return "id, ip, and port must be specified", \ |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 129 | BAD_REQUEST |
| 130 | |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 131 | if self.found_node_index(content) is not None: |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 132 | return "Content Id is already in the list", BAD_REQUEST |
| 133 | |
| 134 | else: |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 135 | self.cluster_config["nodes"].append(content) |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 136 | self.save_to_file() |
| 137 | |
| 138 | return "POST called with content", OK |
| 139 | |
| 140 | def data_put_handler(self, content): |
| 141 | if not is_valid_node_json(content): |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 142 | return "id, ip, and port must be specified", \ |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 143 | BAD_REQUEST |
| 144 | |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 145 | node_index = self.found_node_index(content) |
| 146 | if node_index is not None: |
| 147 | self.cluster_config["nodes"][node_index] = content |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 148 | self.save_to_file() |
| 149 | return "Update succeeded", OK |
| 150 | |
| 151 | else: |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 152 | return "Id %s is not found " % (content["id"]), NOT_FOUND |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 153 | |
| 154 | def data_delete_handler(self, content): |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 155 | node_index = self.found_node_index(content) |
| 156 | if node_index is not None: |
| 157 | del self.cluster_config["nodes"][node_index] |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 158 | self.save_to_file() |
| 159 | return "Deletion succeed.", OK |
| 160 | |
| 161 | else: |
| 162 | return "Id is not found", NOT_FOUND |
| 163 | |
| 164 | """ |
| 165 | This function returns onos cluster information |
| 166 | based on data in memory |
| 167 | """ |
| 168 | |
| 169 | def cluster_responder(self): |
| 170 | |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 171 | return jsonify(self.cluster_config), OK |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 172 | |
| 173 | app = Flask(__name__) |
| 174 | manager = Manager() |
| 175 | |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 176 | |
| 177 | @app.route('/', methods=['GET']) |
| 178 | def data_get_handler(): |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 179 | return manager.data_get_handler() |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 180 | |
| 181 | |
| 182 | @app.route('/cluster.json', methods=['GET']) |
| 183 | def cluster_responder(): |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 184 | return manager.cluster_responder() |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 185 | |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 186 | |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 187 | @app.route('/', methods=['POST']) |
| 188 | def data_post_handler(): |
| 189 | if request.is_json: |
| 190 | content = dict(request.json) |
| 191 | return manager.data_post_handler(content) |
| 192 | else: |
| 193 | return "json required", BAD_REQUEST |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 194 | |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 195 | |
Ray Milkey | ff18b6e | 2017-07-13 08:34:19 -0700 | [diff] [blame^] | 196 | @app.route('/exit', methods=['PUT']) |
| 197 | def data_exit_handler(): |
| 198 | func = request.environ.get('werkzeug.server.shutdown') |
| 199 | if func is None: |
| 200 | raise RuntimeError('Not running with the Werkzeug Server') |
| 201 | func() |
| 202 | return "", OK |
| 203 | |
| 204 | |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 205 | @app.route('/', methods=['PUT']) |
| 206 | def data_put_handler(): |
| 207 | if request.is_json: |
| 208 | content = dict(request.json) |
| 209 | return manager.data_put_handler(content) |
| 210 | else: |
| 211 | return "json data is missing", BAD_REQUEST |
| 212 | |
| 213 | |
| 214 | @app.route('/', methods=['DELETE']) |
| 215 | def data_delete_handler(): |
| 216 | if request.is_json: |
| 217 | content = dict(request.json) |
| 218 | return manager.data_delete_handler(content) |
| 219 | else: |
| 220 | return "No json is found", BAD_REQUEST |
| 221 | |
| 222 | |
| 223 | def main(): |
| 224 | parser = argparse.ArgumentParser( |
| 225 | description="Web server and app to maintain ONOS cluster state.") |
| 226 | parser.add_argument( |
| 227 | '-p', '--persistence-filename', |
| 228 | default='./onos-distributed-manager-persistence.json', |
| 229 | help="Filename used to store persistence information." ) |
| 230 | |
| 231 | args = parser.parse_args() |
| 232 | manager.persistence_filename = args.persistence_filename |
| 233 | manager.load_from_file() |
| 234 | |
| 235 | app.run(host="0.0.0.0", debug=True) |
| 236 | |
Ray Milkey | 644472e | 2017-06-19 13:34:25 -0700 | [diff] [blame] | 237 | |
| 238 | if __name__ == '__main__': |
Ray Milkey | 589b899 | 2017-06-20 12:02:01 -0700 | [diff] [blame] | 239 | manager = Manager() |
| 240 | main() |