From 905b0231e93ce2409a45dd6c4f5f983689fdb790 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Wed, 1 Nov 2017 11:56:50 +0800 Subject: Add compass-deck RESTful API and DB Handlers for Compass Change-Id: I1ce411f279943764c286ea48dca9185d453cf254 Signed-off-by: Harry Huang --- compass-deck/api/__init__.py | 42 + compass-deck/api/api. | 0 compass-deck/api/api.py | 3391 +++++++++++++++++++++++++++ compass-deck/api/api.raml | 4027 +++++++++++++++++++++++++++++++++ compass-deck/api/auth_handler.py | 49 + compass-deck/api/exception_handler.py | 92 + compass-deck/api/utils.py | 35 + compass-deck/api/v1/__init__.py | 0 compass-deck/api/v1/api.py | 248 ++ 9 files changed, 7884 insertions(+) create mode 100644 compass-deck/api/__init__.py create mode 100644 compass-deck/api/api. create mode 100644 compass-deck/api/api.py create mode 100644 compass-deck/api/api.raml create mode 100644 compass-deck/api/auth_handler.py create mode 100644 compass-deck/api/exception_handler.py create mode 100644 compass-deck/api/utils.py create mode 100644 compass-deck/api/v1/__init__.py create mode 100644 compass-deck/api/v1/api.py (limited to 'compass-deck/api') diff --git a/compass-deck/api/__init__.py b/compass-deck/api/__init__.py new file mode 100644 index 0000000..784fe23 --- /dev/null +++ b/compass-deck/api/__init__.py @@ -0,0 +1,42 @@ +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +from flask import Blueprint +from flask.ext.login import LoginManager +from flask import Flask + +# from compass.api.v1.api import v1_app +from compass.utils import setting_wrapper as setting +from compass.utils import util + + +app = Flask(__name__) +app.debug = True +# blueprint = Blueprint('v2_app', __name__) +# app.register_blueprint(v1_app, url_prefix='/v1.0') +# app.register_blueprint(blueprint, url_prefix='/api') + + +app.config['SECRET_KEY'] = 'abcd' +app.config['AUTH_HEADER_NAME'] = setting.USER_AUTH_HEADER_NAME +app.config['REMEMBER_COOKIE_DURATION'] = ( + datetime.timedelta( + seconds=util.parse_time_interval(setting.USER_TOKEN_DURATION) + ) +) + +login_manager = LoginManager() +login_manager.login_view = 'login' +login_manager.init_app(app) diff --git a/compass-deck/api/api. b/compass-deck/api/api. new file mode 100644 index 0000000..e69de29 diff --git a/compass-deck/api/api.py b/compass-deck/api/api.py new file mode 100644 index 0000000..e1cdd39 --- /dev/null +++ b/compass-deck/api/api.py @@ -0,0 +1,3391 @@ +#!/usr/bin/python +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Define all the RestfulAPI entry points.""" + +import datetime +import functools +import logging +import netaddr +import requests +import simplejson as json + +from flask.ext.login import current_user +from flask.ext.login import login_required +from flask.ext.login import login_user +from flask.ext.login import logout_user +from flask import request + +from compass.api import app +from compass.api import auth_handler +from compass.api import exception_handler +from compass.api import utils +from compass.db.api import adapter_holder as adapter_api +from compass.db.api import cluster as cluster_api +from compass.db.api import database +from compass.db.api import health_check_report as health_report_api +from compass.db.api import host as host_api +from compass.db.api import machine as machine_api +from compass.db.api import metadata_holder as metadata_api +from compass.db.api import network as network_api +from compass.db.api import permission as permission_api +from compass.db.api import switch as switch_api +from compass.db.api import user as user_api +from compass.db.api import user_log as user_log_api +from compass.utils import flags +from compass.utils import logsetting +from compass.utils import setting_wrapper as setting +from compass.utils import util + + +def log_user_action(func): + """decorator used to log api request url.""" + @functools.wraps(func) + def decorated_api(*args, **kwargs): + # TODO(xicheng): save request args for GET + # and request data for POST/PUT. + user_log_api.log_user_action(current_user.id, request.path) + return func(*args, **kwargs) + return decorated_api + + +def update_user_token(func): + """decorator used to update user token expire time after api request.""" + @functools.wraps(func) + def decorated_api(*args, **kwargs): + response = func(*args, **kwargs) + expire_timestamp = ( + datetime.datetime.now() + app.config['REMEMBER_COOKIE_DURATION'] + ) + user_api.record_user_token( + current_user.token, expire_timestamp, user=current_user + ) + return response + return decorated_api + + +def _clean_data(data, keys): + """remove keys from dict.""" + for key in keys: + if key in data: + del data[key] + + +def _replace_data(data, key_mapping): + """replace key names in dict.""" + for key, replaced_key in key_mapping.items(): + if key in data: + data[replaced_key] = data[key] + del data[key] + + +def _get_data(data, key): + """get key's value from request arg dict. + + When the value is list, return the element in the list + if the list size is one. If the list size is greater than one, + raise exception_handler.BadRequest. + + Example: data = {'a': ['b'], 'b': 5, 'c': ['d', 'e'], 'd': []} + _get_data(data, 'a') == 'b' + _get_data(data, 'b') == 5 + _get_data(data, 'c') raises exception_handler.BadRequest + _get_data(data, 'd') == None + _get_data(data, 'e') == None + + Usage: Used to parse the key-value pair in request.args to expected types. + Depends on the different flask plugins and what kind of parameters + passed in, the request.args format may be as below: + {'a': 'b'} or {'a': ['b']}. _get_data forces translate the + request.args to the format {'a': 'b'}. It raises exception when some + parameter declares multiple times. + """ + if key in data: + if isinstance(data[key], list): + if data[key]: + if len(data[key]) == 1: + return data[key][0] + else: + raise exception_handler.BadRequest( + '%s declared multi times %s in request' % ( + key, data[key] + ) + ) + else: + return None + else: + return data[key] + else: + return None + + +def _get_data_list(data, key): + """get key's value as list from request arg dict. + + If the value type is list, return it, otherwise return the list + whos only element is the value got from the dict. + + Example: data = {'a': ['b'], 'b': 5, 'c': ['d', 'e'], 'd': []} + _get_data_list(data, 'a') == ['b'] + _get_data_list(data, 'b') == [5] + _get_data_list(data, 'd') == [] + _get_data_list(data, 'e') == [] + + Usage: Used to parse the key-value pair in request.args to expected types. + Depends on the different flask plugins and what kind of parameters + passed in, the request.args format may be as below: + {'a': 'b'} or {'a': ['b']}. _get_data_list forces translate the + request.args to the format {'a': ['b']}. It accepts the case that + some parameter declares multiple times. + """ + if key in data: + if isinstance(data[key], list): + return data[key] + else: + return [data[key]] + else: + return [] + + +def _get_request_data(): + """Convert reqeust data from string to python dict. + + If the request data is not json formatted, raises + exception_handler.BadRequest. + If the request data is not json formatted dict, raises + exception_handler.BadRequest + If the request data is empty, return default as empty dict. + + Usage: It is used to add or update a single resource. + """ + if request.data: + try: + data = json.loads(request.data) + except Exception: + raise exception_handler.BadRequest( + 'request data is not json formatted: %s' % request.data + ) + if not isinstance(data, dict): + raise exception_handler.BadRequest( + 'request data is not json formatted dict: %s' % request.data + ) + return data + else: + return {} + + +def _get_request_data_as_list(): + """Convert reqeust data from string to python list. + + If the request data is not json formatted, raises + exception_handler.BadRequest. + If the request data is not json formatted list, raises + exception_handler.BadRequest. + If the request data is empty, return default as empty list. + + Usage: It is used to batch add or update a list of resources. + """ + if request.data: + try: + data = json.loads(request.data) + except Exception: + raise exception_handler.BadRequest( + 'request data is not json formatted: %s' % request.data + ) + if not isinstance(data, list): + raise exception_handler.BadRequest( + 'request data is not json formatted list: %s' % request.data + ) + return data + else: + return [] + + +def _bool_converter(value): + """Convert string value to bool. + + This function is used to convert value in requeset args to expected type. + If the key exists in request args but the value is not set, it means the + value should be true. + + Examples: + /?is_admin parsed to {'is_admin', None} and it should + be converted to {'is_admin': True}. + /?is_admin=0 parsed and converted to {'is_admin': False}. + /?is_admin=1 parsed and converted to {'is_admin': True}. + """ + if not value: + return True + if value in ['False', 'false', '0']: + return False + if value in ['True', 'true', '1']: + return True + raise exception_handler.BadRequest( + '%r type is not bool' % value + ) + + +def _int_converter(value): + """Convert string value to int. + + We do not use the int converter default exception since we want to make + sure the exact http response code. + + Raises: exception_handler.BadRequest if value can not be parsed to int. + + Examples: + /?count=10 parsed to {'count': '10'} and it should be + converted to {'count': 10}. + """ + try: + return int(value) + except Exception: + raise exception_handler.BadRequest( + '%r type is not int' % value + ) + + +def _get_request_args(**kwargs): + """Get request args as dict. + + The value in the dict is converted to expected type. + + Args: + kwargs: for each key, the value is the type converter. + """ + args = dict(request.args) + logging.log( + logsetting.getLevelByName('fine'), + 'origin request args: %s', args + ) + for key, value in args.items(): + if key in kwargs: + converter = kwargs[key] + if isinstance(value, list): + args[key] = [converter(item) for item in value] + else: + args[key] = converter(value) + logging.log( + logsetting.getLevelByName('fine'), + 'request args: %s', args + ) + return args + + +def _group_data_action(data, **data_callbacks): + """Group api actions and pass data to grouped action callback. + + Example: + data = { + 'add_hosts': [{'name': 'a'}, {'name': 'b'}], + 'update_hosts': {'c': {'mac': '123'}}, + 'remove_hosts': ['d', 'e'] + } + data_callbacks = { + 'add_hosts': update_cluster_action, + 'update_hosts': update_cluster_action, + 'remove_hosts': update_cluster_action + } + it converts to update_cluster_action( + add_hosts=[{'name': 'a'}, {'name': 'b'}], + update_hosts={'c': {'mac': '123'}}, + remove_hosts=['d', 'e'] + ) + + Raises: + exception_handler.BadRequest if data is empty. + exception_handler.BadMethod if there are some keys in data but + not in data_callbacks. + exception_handler.BadRequest if it groups to multiple + callbacks. + """ + if not data: + raise exception_handler.BadRequest( + 'no action to take' + ) + unsupported_keys = list(set(data) - set(data_callbacks)) + if unsupported_keys: + raise exception_handler.BadMethod( + 'unsupported actions: %s' % unsupported_keys + ) + callback_datas = {} + for data_key, data_value in data.items(): + callback = data_callbacks[data_key] + callback_datas.setdefault(id(callback), {})[data_key] = data_value + if len(callback_datas) > 1: + raise exception_handler.BadRequest( + 'multi actions are not supported' + ) + callback_ids = {} + for data_key, data_callback in data_callbacks.items(): + callback_ids[id(data_callback)] = data_callback + for callback_id, callback_data in callback_datas.items(): + return callback_ids[callback_id](**callback_data) + + +def _wrap_response(func, response_code): + """wrap function response to json formatted http response.""" + def wrapped_func(*args, **kwargs): + return utils.make_json_response( + response_code, + func(*args, **kwargs) + ) + return wrapped_func + + +def _reformat_host_networks(networks): + """Reformat networks from list to dict. + + The key in the dict is the value of the key 'interface' + in each network. + + Example: networks = [{'interface': 'eth0', 'ip': '10.1.1.1'}] + is reformatted to { + 'eth0': {'interface': 'eth0', 'ip': '10.1.1.1'} + } + + Usage: The networks got from db api is a list of network, + For better parsing in json frontend, we converted the + format into dict to easy reference. + """ + network_mapping = {} + for network in networks: + if 'interface' in network: + network_mapping[network['interface']] = network + return network_mapping + + +def _reformat_host(host): + """Reformat host's networks.""" + if isinstance(host, list): + return [_reformat_host(item) for item in host] + if 'networks' in host: + host['networks'] = _reformat_host_networks(host['networks']) + return host + + +def _login(use_cookie): + """User login helper function. + + The request data should contain at least 'email' and 'password'. + The cookie expiration duration is defined in flask app config. + If user is not authenticated, it raises Unauthorized exception. + """ + data = _get_request_data() + if 'email' not in data or 'password' not in data: + raise exception_handler.BadRequest( + 'missing email or password in data' + ) + expire_timestamp = ( + datetime.datetime.now() + app.config['REMEMBER_COOKIE_DURATION'] + ) + data['expire_timestamp'] = expire_timestamp + user = auth_handler.authenticate_user(**data) + if not user.active: + raise exception_handler.UserDisabled( + '%s is not activated' % user.email + ) + if not login_user(user, remember=data.get('remember', False)): + raise exception_handler.UserDisabled('failed to login: %s' % user) + + user_log_api.log_user_action(user.id, request.path) + response_data = user_api.record_user_token( + user.token, user.expire_timestamp, user=user + ) + return utils.make_json_response(200, response_data) + + +@app.route('/users/token', methods=['POST']) +def get_token(): + """user login and return token.""" + return _login(False) + + +@app.route("/users/login", methods=['POST']) +def login(): + """User login.""" + return _login(True) + + +@app.route("/users/register", methods=['POST']) +def register(): + """register new user.""" + data = _get_request_data() + data['is_admin'] = False + data['active'] = False + return utils.make_json_response( + 200, user_api.add_user(**data) + ) + + +@app.route('/users/logout', methods=['POST']) +@login_required +def logout(): + """User logout.""" + user_log_api.log_user_action(current_user.id, request.path) + response_data = user_api.clean_user_token( + current_user.token, user=current_user + ) + logout_user() + return utils.make_json_response(200, response_data) + + +@app.route("/users", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_users(): + """list users. + + Supported paramters: ['email', 'is_admin', 'active'] + """ + data = _get_request_args( + is_admin=_bool_converter, + active=_bool_converter + ) + return utils.make_json_response( + 200, user_api.list_users(user=current_user, **data) + ) + + +@app.route("/users", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_user(): + """add user. + + Must parameters: ['email', 'password'], + Optional paramters: ['is_admin', 'active'] + """ + data = _get_request_data() + user_dict = user_api.add_user(user=current_user, **data) + return utils.make_json_response( + 200, user_dict + ) + + +@app.route("/users/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_user(user_id): + """Get user by id.""" + data = _get_request_args() + return utils.make_json_response( + 200, user_api.get_user(user_id, user=current_user, **data) + ) + + +@app.route("/current-user", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_current_user(): + """Get current user.""" + data = _get_request_args() + return utils.make_json_response( + 200, user_api.get_current_user(user=current_user, **data) + ) + + +@app.route("/users/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_user(user_id): + """Update user. + + Supported parameters by self: [ + 'email', 'firstname', 'lastname', 'password' + ] + Supported parameters by admin ['is_admin', 'active'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + user_api.update_user( + user_id, + user=current_user, + **data + ) + ) + + +@app.route("/users/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_user(user_id): + """Delete user. + + Delete is only permitted by admin user. + """ + data = _get_request_data() + return utils.make_json_response( + 200, + user_api.del_user( + user_id, user=current_user, **data + ) + ) + + +@app.route("/users//permissions", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_user_permissions(user_id): + """Get user permissions.""" + data = _get_request_args() + return utils.make_json_response( + 200, user_api.get_permissions(user_id, user=current_user, **data) + ) + + +@app.route("/users//action", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def take_user_action(user_id): + """Take user action. + + Support actions: [ + 'add_permissions', 'remove_permissions', + 'set_permissions', 'enable_user', + 'disable_user' + ] + """ + data = _get_request_data() + update_permissions_func = _wrap_response( + functools.partial( + user_api.update_permissions, user_id, user=current_user, + ), + 200 + ) + + def disable_user(disable_user=None): + return user_api.update_user( + user_id, user=current_user, active=False + ) + + disable_user_func = _wrap_response( + disable_user, + 200 + ) + + def enable_user(enable_user=None): + return user_api.update_user( + user_id, user=current_user, active=True + ) + + enable_user_func = _wrap_response( + enable_user, + 200 + ) + return _group_data_action( + data, + add_permissions=update_permissions_func, + remove_permissions=update_permissions_func, + set_permissions=update_permissions_func, + enable_user=enable_user_func, + disable_user=disable_user_func + ) + + +@app.route( + '/users//permissions/', + methods=['GET'] +) +@log_user_action +@login_required +@update_user_token +def show_user_permission(user_id, permission_id): + """Get a specific user permission.""" + data = _get_request_args() + return utils.make_json_response( + 200, + user_api.get_permission( + user_id, permission_id, user=current_user, + **data + ) + ) + + +@app.route("/users//permissions", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_user_permission(user_id): + """Add permission to a specific user. + + add_user_permission is only permitted by admin user. + Must parameters: ['permission_id'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + user_api.add_permission( + user_id, user=current_user, + **data + ) + ) + + +@app.route( + '/users//permissions/', + methods=['DELETE'] +) +@log_user_action +@login_required +@update_user_token +def delete_user_permission(user_id, permission_id): + """Delete a specific user permission.""" + data = _get_request_data() + return utils.make_json_response( + 200, + user_api.del_permission( + user_id, permission_id, user=current_user, + **data + ) + ) + + +@app.route("/permissions", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_permissions(): + """List permissions. + + Supported filters: ['id', 'name', 'alias', 'description'] + """ + data = _get_request_args() + return utils.make_json_response( + 200, + permission_api.list_permissions(user=current_user, **data) + ) + + +@app.route("/permissions/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_permission(permission_id): + """Get permission.""" + data = _get_request_args() + return utils.make_json_response( + 200, + permission_api.get_permission(permission_id, user=current_user, **data) + ) + + +def _filter_timestamp(data): + """parse timestamp related params to db api understandable params. + + Example: + {'timestamp_start': '2005-12-23 12:00:00'} to + {'timestamp': {'ge': timestamp('2005-12-23 12:00:00')}}, + {'timestamp_end': '2005-12-23 12:00:00'} to + {'timestamp': {'le': timestamp('2005-12-23 12:00:00')}}, + {'timestamp_range': '2005-12-23 12:00:00,2005-12-24 12:00:00'} to + {'timestamp': {'between': [ + timestamp('2005-12-23 12:00:00'), + timestamp('2005-12-24 12:00:00') + ] + }} + + The timestamp related params can be declared multi times. + """ + timestamp_filter = {} + start = _get_data(data, 'timestamp_start') + if start is not None: + timestamp_filter['ge'] = util.parse_datetime( + start, exception_handler.BadRequest + ) + end = _get_data(data, 'timestamp_end') + if end is not None: + timestamp_filter['le'] = util.parse_datetime( + end, exception_handler.BadRequest) + range = _get_data_list(data, 'timestamp_range') + if range: + timestamp_filter['between'] = [] + for value in range: + timestamp_filter['between'].append( + util.parse_datetime_range( + value, exception_handler.BadRequest + ) + ) + data['timestamp'] = timestamp_filter + _clean_data( + data, + [ + 'timestamp_start', 'timestamp_end', + 'timestamp_range' + ] + ) + + +@app.route("/users/logs", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_all_user_actions(): + """List all users actions. + + Supported filters: [ + 'timestamp_start', 'timestamp_end', 'timestamp_range', + 'user_email' + ] + """ + data = _get_request_args() + _filter_timestamp(data) + return utils.make_json_response( + 200, + user_log_api.list_actions( + user=current_user, **data + ) + ) + + +@app.route("/users//logs", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_user_actions(user_id): + """List user actions for specific user. + + Supported filters: [ + 'timestamp_start', 'timestamp_end', 'timestamp_range', + ] + """ + data = _get_request_args() + _filter_timestamp(data) + return utils.make_json_response( + 200, + user_log_api.list_user_actions( + user_id, user=current_user, **data + ) + ) + + +@app.route("/users/logs", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_all_user_actions(): + """Delete all user actions.""" + data = _get_request_data() + return utils.make_json_response( + 200, + user_log_api.del_actions( + user=current_user, **data + ) + ) + + +@app.route("/users//logs", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_user_actions(user_id): + """Delete user actions for specific user.""" + data = _get_request_data() + return utils.make_json_response( + 200, + user_log_api.del_user_actions( + user_id, user=current_user, **data + ) + ) + + +def _filter_switch_ip(data): + """filter switch ip related params to db/api understandable format. + + Examples: + {'switchIp': '10.0.0.1'} to {'ip_int': {'eq': int of '10.0.0.1'}} + {'switchIpStart': '10.0.0.1'} to + {'ip_int': {'ge': int of '10.0.0.1'}} + {'switchIpEnd': '10.0.0.1'} to + {'ip_int': {'le': int of '10.0.0.1'}} + {'switchIpRange': '10.0.0.1,10.0.0.254'} to + {'ip_int': {'between': [int of '10.0.0.1', int of '10.0.0.254']}} + + the switch ip related params can be declared multi times. + """ + ip_filter = {} + switch_ips = _get_data_list(data, 'switchIp') + if switch_ips: + ip_filter['eq'] = [] + for switch_ip in switch_ips: + ip_filter['eq'].append(long(netaddr.IPAddress(switch_ip))) + switch_start = _get_data(data, 'switchIpStart') + if switch_start is not None: + ip_filter['ge'] = long(netaddr.IPAddress(switch_start)) + switch_end = _get_data(data, 'switchIpEnd') + if switch_end is not None: + ip_filter['lt'] = long(netaddr.IPAddress(switch_end)) + switch_nets = _get_data_list(data, 'switchIpNetwork') + if switch_nets: + ip_filter['between'] = [] + for switch_net in switch_nets: + network = netaddr.IPNetwork(switch_net) + ip_filter['between'].append((network.first, network.last)) + switch_ranges = _get_data_list(data, 'switchIpRange') + if switch_ranges: + ip_filter.setdefault('between', []) + for switch_range in switch_ranges: + ip_start, ip_end = switch_range.split(',') + ip_filter['between'].append( + long(netaddr.IPAddress(ip_start)), + long(netaddr.IPAddress(ip_end)) + ) + if ip_filter: + data['ip_int'] = ip_filter + _clean_data( + data, + [ + 'switchIp', 'switchIpStart', 'switchIpEnd', + 'switchIpNetwork', 'switchIpRange' + ] + ) + + +@app.route("/switches", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_switches(): + """List switches. + + Supported filters: [ + 'switchIp', 'switchIpStart', 'switchIpEnd', + 'switchIpEnd', 'vendor', 'state' + ] + """ + data = _get_request_args() + _filter_switch_ip(data) + return utils.make_json_response( + 200, + switch_api.list_switches( + user=current_user, **data + ) + ) + + +@app.route("/switches/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_switch(switch_id): + """Get switch.""" + data = _get_request_args() + return utils.make_json_response( + 200, switch_api.get_switch(switch_id, user=current_user, **data) + ) + + +@app.route("/switches", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_switch(): + """add switch. + + Must fields: ['ip'] + Optional fields: [ + 'credentials', 'vendor', 'state', + 'err_msg', 'filters' + ] + """ + data = _get_request_data() + _replace_data(data, {'filters': 'machine_filters'}) + return utils.make_json_response( + 200, + switch_api.add_switch(user=current_user, **data) + ) + + +@app.route("/switchesbatch", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_switches(): + """batch add switches. + + request data is a list of dict. Each dict must contain ['ip'], + may contain [ + 'credentials', 'vendor', 'state', 'err_msg', 'filters' + ] + """ + data = _get_request_data_as_list() + for item_data in data: + _replace_data(item_data, {'filters': 'machine_filters'}) + return utils.make_json_response( + 200, + switch_api.add_switches( + data=data, user=current_user + ) + ) + + +@app.route("/switches/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_switch(switch_id): + """update switch. + + Supported fields: [ + 'ip', 'credentials', 'vendor', 'state', + 'err_msg', 'filters' + ] + """ + data = _get_request_data() + _replace_data(data, {'filters': 'machine_filters'}) + return utils.make_json_response( + 200, + switch_api.update_switch(switch_id, user=current_user, **data) + ) + + +@app.route("/switches/", methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def patch_switch(switch_id): + """patch switch. + + Supported fields: [ + 'credentials', 'filters' + ] + """ + data = _get_request_data() + _replace_data(data, {'filters': 'machine_filters'}) + return utils.make_json_response( + 200, + switch_api.patch_switch(switch_id, user=current_user, **data) + ) + + +@app.route("/switches/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_switch(switch_id): + """delete switch.""" + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.del_switch(switch_id, user=current_user, **data) + ) + + +@util.deprecated +@app.route("/switch-filters", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_switch_filters(): + """List switch filters.""" + data = _get_request_args() + _filter_switch_ip(data) + return utils.make_json_response( + 200, + switch_api.list_switch_filters( + user=current_user, **data + ) + ) + + +@util.deprecated +@app.route("/switch-filters/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_switch_filters(switch_id): + """Get switch filters.""" + data = _get_request_args() + return utils.make_json_response( + 200, + switch_api.get_switch_filters(switch_id, user=current_user, **data) + ) + + +@util.deprecated +@app.route("/switch-filters/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_switch_filters(switch_id): + """update switch filters.""" + data = _get_request_data() + _replace_data(data, {'filters': 'machine_filters'}) + return utils.make_json_response( + 200, + switch_api.update_switch_filters(switch_id, user=current_user, **data) + ) + + +@util.deprecated +@app.route("/switch-filters/", methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def patch_switch_filters(switch_id): + """patch switch filters.""" + data = _get_request_data() + _replace_data(data, {'filters': 'machine_filters'}) + return utils.make_json_response( + 200, + switch_api.patch_switch_filter(switch_id, user=current_user, **data) + ) + + +def _filter_switch_port(data): + """Generate switch machine filters by switch port related fields. + + Examples: + {'port': 'ae20'} to {'port': {'eq': 'ae20'}} + {'portStart': 20, 'portPrefix': 'ae', 'portSuffix': ''} to + {'port': {'startswith': 'ae', 'endswith': '', 'resp_ge': 20}} + {'portEnd': 20, 'portPrefix': 'ae', 'portSuffix': ''} to + {'port': {'startswith': 'ae', 'endswith': '', 'resp_le': 20}} + {'portRange': '20,40', 'portPrefix': 'ae', 'portSuffix': ''} to + {'port': { + 'startswith': 'ae', 'endswith': '', 'resp_range': [(20. 40)] + }} + + For each switch machines port, it extracts portNumber from + '' and filter the returned switch + machines by the filters. + """ + port_filter = {} + ports = _get_data_list(data, 'port') + if ports: + port_filter['eq'] = ports + port_start = _get_data(data, 'portStart') + if port_start is not None: + port_filter['resp_ge'] = int(port_start) + port_end = _get_data(data, 'portEnd') + if port_end is not None: + port_filter['resp_lt'] = int(port_end) + port_ranges = _get_data_list(data, 'portRange') + if port_ranges: + port_filter['resp_range'] = [] + for port_range in port_ranges: + port_start, port_end = port_range.split(',') + port_filter['resp_range'].append( + (int(port_start), int(port_end)) + ) + port_prefix = _get_data(data, 'portPrefix') + if port_prefix: + port_filter['startswith'] = port_prefix + port_suffix = _get_data(data, 'portSuffix') + if port_suffix: + port_filter['endswith'] = port_suffix + if port_filter: + data['port'] = port_filter + _clean_data( + data, + [ + 'portStart', 'portEnd', 'portRange', + 'portPrefix', 'portSuffix' + ] + ) + + +def _filter_general(data, key): + """Generate general filter for db/api returned list. + + Supported filter type: [ + 'resp_eq', 'resp_in', 'resp_le', 'resp_ge', + 'resp_gt', 'resp_lt', 'resp_match' + ] + """ + general_filter = {} + general = _get_data_list(data, key) + if general: + general_filter['resp_in'] = general + data[key] = general_filter + + +def _filter_machine_tag(data): + """Generate filter for machine tag. + + Examples: + original returns: + [{'tag': { + 'city': 'beijing', + 'building': 'tsinghua main building', + 'room': '205', 'rack': 'a2b3', + 'stack': '20' + }},{'location': { + 'city': 'beijing', + 'building': 'tsinghua main building', + 'room': '205', 'rack': 'a2b2', + 'stack': '20' + }}] + filter: {'tag': 'room=205;rack=a2b3'} + filtered: [{'tag': { + 'city': 'beijing', + 'building': 'tsinghua main building', + 'room': '205', 'rack': 'a2b3', + 'stack': '20' + }}] + """ + tag_filter = {} + tags = _get_data_list(data, 'tag') + if tags: + tag_filter['resp_in'] = [] + for tag in tags: + tag_filter['resp_in'].append( + util.parse_request_arg_dict(tag) + ) + data['tag'] = tag_filter + + +def _filter_machine_location(data): + """Generate filter for machine location. + + Examples: + original returns: + [{'location': { + 'city': 'beijing', + 'building': 'tsinghua main building', + 'room': '205', 'rack': 'a2b3', + 'stack': '20' + }},{'location': { + 'city': 'beijing', + 'building': 'tsinghua main building', + 'room': '205', 'rack': 'a2b2', + 'stack': '20' + }}] + filter: {'location': 'room=205;rack=a2b3'} + filtered: [{'location': { + 'city': 'beijing', + 'building': 'tsinghua main building', + 'room': '205', 'rack': 'a2b3', + 'stack': '20' + }}] + """ + location_filter = {} + locations = _get_data_list(data, 'location') + if locations: + location_filter['resp_in'] = [] + for location in locations: + location_filter['resp_in'].append( + util.parse_request_arg_dict(location) + ) + data['location'] = location_filter + + +@app.route("/switches//machines", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_switch_machines(switch_id): + """Get switch machines. + + Supported filters: [ + 'port', 'portStart', 'portEnd', 'portRange', + 'portPrefix', 'portSuffix', 'vlans', 'tag', 'location' + ] + """ + data = _get_request_args(vlans=_int_converter) + _filter_switch_port(data) + _filter_general(data, 'vlans') + _filter_machine_tag(data) + _filter_machine_location(data) + return utils.make_json_response( + 200, + switch_api.list_switch_machines( + switch_id, user=current_user, **data + ) + ) + + +@app.route("/switches//machines-hosts", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_switch_machines_hosts(switch_id): + """Get switch machines or hosts. + + Supported filters: [ + 'port', 'portStart', 'portEnd', 'portRange', + 'portPrefix', 'portSuffix', 'vlans', 'tag', 'location', + 'os_name', 'os_id' + ] + + """ + data = _get_request_args(vlans=_int_converter, os_id=_int_converter) + _filter_switch_port(data) + _filter_general(data, 'vlans') + _filter_machine_tag(data) + _filter_machine_location(data) + _filter_general(data, 'os_name') + # TODO(xicheng): os_id filter should be removed later + _filter_general(data, 'os_id') + return utils.make_json_response( + 200, + switch_api.list_switch_machines_hosts( + switch_id, user=current_user, **data + ) + ) + + +@app.route("/switches//machines", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_switch_machine(switch_id): + """add switch machine. + + Must fields: ['mac', 'port'] + Optional fields: ['vlans', 'ipmi_credentials', 'tag', 'location'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.add_switch_machine(switch_id, user=current_user, **data) + ) + + +@app.route("/switches/machines", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_switch_machines(): + """batch add switch machines. + + request data is list of dict which contains switch machine fields. + Each dict must contain ['switch_ip', 'mac', 'port'], + may contain ['vlans', 'ipmi_credentials', 'tag', 'location']. + """ + data = _get_request_data_as_list() + return utils.make_json_response( + 200, switch_api.add_switch_machines( + data=data, user=current_user + ) + ) + + +@app.route( + '/switches//machines/', + methods=['GET'] +) +@log_user_action +@login_required +@update_user_token +def show_switch_machine(switch_id, machine_id): + """get switch machine.""" + data = _get_request_args() + return utils.make_json_response( + 200, + switch_api.get_switch_machine( + switch_id, machine_id, user=current_user, **data + ) + ) + + +@app.route( + '/switches//machines/', + methods=['PUT'] +) +@log_user_action +@login_required +@update_user_token +def update_switch_machine(switch_id, machine_id): + """update switch machine. + + Supported fields: [ + 'port', 'vlans', 'ipmi_credentials', 'tag', 'location' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.update_switch_machine( + switch_id, machine_id, user=current_user, **data + ) + ) + + +@app.route( + '/switches//machines/', + methods=['PATCH'] +) +@log_user_action +@login_required +@update_user_token +def patch_switch_machine(switch_id, machine_id): + """patch switch machine. + + Supported fields: [ + 'vlans', 'ipmi_credentials', 'tag', 'location' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.patch_switch_machine( + current_user, switch_id, machine_id, **data + ) + ) + + +@app.route( + '/switches//machines/', + methods=['DELETE'] +) +@log_user_action +@login_required +@update_user_token +def delete_switch_machine(switch_id, machine_id): + """Delete switch machine.""" + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.del_switch_machine( + switch_id, machine_id, user=current_user, **data + ) + ) + + +@app.route("/switches//action", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def take_switch_action(switch_id): + """take switch action. + + Supported actions: [ + 'find_machines', 'add_machines', 'remove_machines', + 'set_machines' + ] + """ + data = _get_request_data() + poll_switch_func = _wrap_response( + functools.partial( + switch_api.poll_switch, switch_id, user=current_user, + ), + 202 + ) + update_switch_machines_func = _wrap_response( + functools.partial( + switch_api.update_switch_machines, switch_id, user=current_user, + ), + 200 + ) + return _group_data_action( + data, + find_machines=poll_switch_func, + add_machines=update_switch_machines_func, + remove_machines=update_switch_machines_func, + set_machines=update_switch_machines_func + ) + + +@app.route("/machines//action", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def take_machine_action(machine_id): + """take machine action. + + Supported actions: ['tag', 'poweron', 'poweroff', 'reset'] + """ + data = _get_request_data() + tag_func = _wrap_response( + functools.partial( + machine_api.update_machine, machine_id, user=current_user, + ), + 200 + ) + poweron_func = _wrap_response( + functools.partial( + machine_api.poweron_machine, machine_id, user=current_user, + ), + 202 + ) + poweroff_func = _wrap_response( + functools.partial( + machine_api.poweroff_machine, machine_id, user=current_user, + ), + 202 + ) + reset_func = _wrap_response( + functools.partial( + machine_api.reset_machine, machine_id, user=current_user, + ), + 202 + ) + return _group_data_action( + data, + tag=tag_func, + poweron=poweron_func, + poweroff=poweroff_func, + reset=reset_func + ) + + +@app.route("/switch-machines", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_switchmachines(): + """List switch machines. + + Supported filters: [ + 'vlans', 'switchIp', 'SwitchIpStart', + 'SwitchIpEnd', 'SwitchIpRange', 'port', + 'portStart', 'portEnd', 'portRange', + 'location', 'tag', 'mac' + ] + """ + data = _get_request_args(vlans=_int_converter) + _filter_switch_ip(data) + _filter_switch_port(data) + _filter_general(data, 'vlans') + _filter_machine_tag(data) + _filter_machine_location(data) + return utils.make_json_response( + 200, + switch_api.list_switchmachines( + user=current_user, **data + ) + ) + + +@app.route("/switches-machines-hosts", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_switchmachines_hosts(): + """List switch machines or hosts. + + Supported filters: [ + 'vlans', 'switchIp', 'SwitchIpStart', + 'SwitchIpEnd', 'SwitchIpRange', 'port', + 'portStart', 'portEnd', 'portRange', + 'location', 'tag', 'mac', 'os_name' + ] + + """ + data = _get_request_args(vlans=_int_converter, os_id=_int_converter) + _filter_switch_ip(data) + _filter_switch_port(data) + _filter_general(data, 'vlans') + _filter_machine_tag(data) + _filter_machine_location(data) + _filter_general(data, 'os_name') + return utils.make_json_response( + 200, + switch_api.list_switchmachines_hosts( + user=current_user, **data + ) + ) + + +@app.route( + '/switch-machines/', + methods=['GET'] +) +@log_user_action +@login_required +@update_user_token +def show_switchmachine(switch_machine_id): + """get switch machine.""" + data = _get_request_args() + return utils.make_json_response( + 200, + switch_api.get_switchmachine( + switch_machine_id, user=current_user, **data + ) + ) + + +@app.route( + '/switch-machines/', + methods=['PUT'] +) +@log_user_action +@login_required +@update_user_token +def update_switchmachine(switch_machine_id): + """update switch machine. + + Support fields: [ + ''port', 'vlans', 'ipmi_credentials', 'tag', 'location' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.update_switchmachine( + switch_machine_id, user=current_user, **data + ) + ) + + +@app.route('/switch-machines/', methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def patch_switchmachine(switch_machine_id): + """patch switch machine. + + Support fields: [ + 'vlans', 'ipmi_credentials', 'tag', 'location' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.patch_switchmachine( + switch_machine_id, user=current_user, **data + ) + ) + + +@app.route("/switch-machines/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_switchmachine(switch_machine_id): + """Delete switch machine.""" + data = _get_request_data() + return utils.make_json_response( + 200, + switch_api.del_switchmachine( + switch_machine_id, user=current_user, **data + ) + ) + + +@app.route("/machines", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_machines(): + """List machines. + + Supported filters: [ + 'tag', 'location', 'mac' + ] + """ + data = _get_request_args() + _filter_machine_tag(data) + _filter_machine_location(data) + return utils.make_json_response( + 200, + machine_api.list_machines( + user=current_user, **data + ) + ) + + +@app.route("/machine/discovery", methods=['POST']) +def switch_discovery(): + """switch on/off hardware discovery""" + data = _get_request_args() + + +@app.route("/machines", methods=['POST']) +def add_machine(): + """add machine by tinycore. + + supported fileds: [ + 'tag', 'location', 'ipmi_credentials', + 'machine_attributes' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + machine_api.add_machine(**data) + ) + + +@app.route("/machines/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_machine(machine_id): + """Get machine.""" + data = _get_request_args() + return utils.make_json_response( + 200, + machine_api.get_machine( + machine_id, user=current_user, **data + ) + ) + + +@app.route("/machines/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_machine(machine_id): + """update machine. + + Supported fields: [ + 'tag', 'location', 'ipmi_credentials', + 'machine_attributes' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + machine_api.update_machine( + machine_id, user=current_user, **data + ) + ) + + +@app.route("/machines/", methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def patch_machine(machine_id): + """patch machine. + + Supported fields: [ + 'tag', 'location', 'ipmi_credentials', + 'machine_attributes' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + machine_api.patch_machine( + machine_id, user=current_user, **data + ) + ) + + +@app.route("/machines/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_machine(machine_id): + """Delete machine.""" + data = _get_request_data() + return utils.make_json_response( + 200, + machine_api.del_machine( + machine_id, user=current_user, **data + ) + ) + + +@app.route("/subnets", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_subnets(): + """List subnets. + + Supported filters: [ + 'subnet', 'name' + ] + """ + data = _get_request_args() + return utils.make_json_response( + 200, + network_api.list_subnets( + user=current_user, **data + ) + ) + + +@app.route("/subnets/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_subnet(subnet_id): + """Get subnet.""" + data = _get_request_args() + return utils.make_json_response( + 200, + network_api.get_subnet( + subnet_id, user=current_user, **data + ) + ) + + +@app.route("/subnets", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_subnet(): + """add subnet. + + Must fields: ['subnet'] + Optional fields: ['name'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + network_api.add_subnet(user=current_user, **data) + ) + + +@app.route("/subnets/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_subnet(subnet_id): + """update subnet. + + Support fields: ['subnet', 'name'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + network_api.update_subnet( + subnet_id, user=current_user, **data + ) + ) + + +@app.route("/subnets/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_subnet(subnet_id): + """Delete subnet.""" + data = _get_request_data() + return utils.make_json_response( + 200, + network_api.del_subnet( + subnet_id, user=current_user, **data + ) + ) + + +@app.route("/adapters", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_adapters(): + """List adapters. + + Supported filters: [ + 'name' + ] + """ + data = _get_request_args() + _filter_general(data, 'name') + return utils.make_json_response( + 200, + adapter_api.list_adapters( + user=current_user, **data + ) + ) + + +@app.route("/adapters/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_adapter(adapter_id): + """Get adapter.""" + data = _get_request_args() + return utils.make_json_response( + 200, + adapter_api.get_adapter( + adapter_id, user=current_user, **data + ) + ) + + +@app.route("/adapters//metadata", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_adapter_metadata(adapter_id): + """Get adapter metadata.""" + data = _get_request_args() + return utils.make_json_response( + 200, + metadata_api.get_package_metadata( + adapter_id, user=current_user, **data + ) + ) + + +@app.route("/oses//metadata", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_os_metadata(os_id): + """Get os metadata.""" + data = _get_request_args() + return utils.make_json_response( + 200, + metadata_api.get_os_metadata( + os_id, user=current_user, **data + ) + ) + + +@app.route("/oses//ui_metadata", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def convert_os_metadata(os_id): + """Convert os metadata to ui os metadata.""" + data = _get_request_args() + return utils.make_json_response( + 200, + metadata_api.get_os_ui_metadata( + os_id, user=current_user, **data + ) + ) + + +@app.route("/flavors//metadata", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_flavor_metadata(flavor_id): + """Get flavor metadata.""" + data = _get_request_args() + return utils.make_json_response( + 200, + metadata_api.get_flavor_metadata( + flavor_id, user=current_user, **data + ) + ) + + +@app.route("/flavors//ui_metadata", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def convert_flavor_metadata(flavor_id): + """Convert flavor metadata to ui flavor metadata.""" + data = _get_request_args() + return utils.make_json_response( + 200, + metadata_api.get_flavor_ui_metadata( + flavor_id, user=current_user, **data + ) + ) + + +@app.route( + "/adapters//oses//metadata", + methods=['GET'] +) +@log_user_action +@login_required +@update_user_token +def show_adapter_os_metadata(adapter_id, os_id): + """Get adapter metadata.""" + data = _get_request_args() + return utils.make_json_response( + 200, + metadata_api.get_package_os_metadata( + adapter_id, os_id, user=current_user, **data + ) + ) + + +@app.route("/clusters", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_clusters(): + """List clusters. + + Supported filters: [ + 'name', 'os_name', 'owner', 'adapter_name', 'flavor_name' + ] + """ + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.list_clusters( + user=current_user, **data + ) + ) + + +@app.route("/clusters/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_cluster(cluster_id): + """Get cluster.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_cluster( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_cluster(): + """add cluster. + + Must fields: ['name', 'adapter_id', 'os_id'] + Optional fields: ['flavor_id'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.add_cluster(user=current_user, **data) + ) + + +@app.route("/clusters/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_cluster(cluster_id): + """update cluster. + + Supported fields: ['name', 'reinstall_distributed_system'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_cluster( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_cluster(cluster_id): + """Delete cluster.""" + data = _get_request_data() + response = cluster_api.del_cluster( + cluster_id, user=current_user, **data + ) + if 'status' in response: + return utils.make_json_response( + 202, response + ) + else: + return utils.make_json_response( + 200, response + ) + + +@app.route("/clusters//config", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_cluster_config(cluster_id): + """Get cluster config.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_cluster_config( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters//metadata", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_cluster_metadata(cluster_id): + """Get cluster metadata.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_cluster_metadata( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters//config", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_cluster_config(cluster_id): + """update cluster config. + + Supported fields: ['os_config', 'package_config', 'config_step'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_cluster_config( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters//config", methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def patch_cluster_config(cluster_id): + """patch cluster config. + + Supported fields: ['os_config', 'package_config', 'config_step'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.patch_cluster_config(cluster_id, user=current_user, **data) + ) + + +@app.route("/clusters//config", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_cluster_config(cluster_id): + """Delete cluster config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.del_cluster_config( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters//action", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def take_cluster_action(cluster_id): + """take cluster action. + + Supported actions: [ + 'add_hosts', 'remove_hosts', 'set_hosts', + 'review', 'deploy', 'check_health', 'apply_patch' + ] + """ + data = _get_request_data() + url_root = request.url_root + + update_cluster_hosts_func = _wrap_response( + functools.partial( + cluster_api.update_cluster_hosts, cluster_id, user=current_user, + ), + 200 + ) + review_cluster_func = _wrap_response( + functools.partial( + cluster_api.review_cluster, cluster_id, user=current_user, + ), + 200 + ) + deploy_cluster_func = _wrap_response( + functools.partial( + cluster_api.deploy_cluster, cluster_id, user=current_user, + ), + 202 + ) + redeploy_cluster_func = _wrap_response( + functools.partial( + cluster_api.redeploy_cluster, cluster_id, user=current_user, + ), + 202 + ) + patch_cluster_func = _wrap_response( + functools.partial( + cluster_api.patch_cluster, cluster_id, user=current_user, + ), + 202 + ) + check_cluster_health_func = _wrap_response( + functools.partial( + health_report_api.start_check_cluster_health, + cluster_id, + '%s/clusters/%s/healthreports' % (url_root, cluster_id), + user=current_user + ), + 202 + ) + return _group_data_action( + data, + add_hosts=update_cluster_hosts_func, + set_hosts=update_cluster_hosts_func, + remove_hosts=update_cluster_hosts_func, + review=review_cluster_func, + deploy=deploy_cluster_func, + redeploy=redeploy_cluster_func, + apply_patch=patch_cluster_func, + check_health=check_cluster_health_func + ) + + +@app.route("/clusters//state", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def get_cluster_state(cluster_id): + """Get cluster state.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_cluster_state( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters//healthreports", methods=['POST']) +def create_health_reports(cluster_id): + """Create a health check report. + + Must fields: ['name'] + Optional fields: [ + 'display_name', 'report', 'category', 'state', 'error_message' + ] + """ + data = _get_request_data() + output = [] + logging.info('create_health_reports for cluster %s: %s', + cluster_id, data) + if 'report_list' in data: + for report in data['report_list']: + try: + output.append( + health_report_api.add_report_record( + cluster_id, **report + ) + ) + except Exception as error: + logging.exception(error) + continue + + else: + output = health_report_api.add_report_record( + cluster_id, **data + ) + + return utils.make_json_response( + 200, + output + ) + + +@app.route("/clusters//healthreports", methods=['PUT']) +def bulk_update_reports(cluster_id): + """Bulk update reports. + + request data is a list of health report. + Each health report must contain ['name'], + may contain [ + 'display_name', 'report', 'category', 'state', 'error_message' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + health_report_api.update_multi_reports( + cluster_id, **data + ) + ) + + +@app.route("/clusters//healthreports", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_health_reports(cluster_id): + """list health report for a cluster.""" + data = _get_request_data() + return utils.make_json_response( + 200, + health_report_api.list_health_reports( + cluster_id, user=current_user, **data + ) + ) + + +@app.route("/clusters//healthreports/", methods=['PUT']) +def update_health_report(cluster_id, name): + """Update cluster health report. + + Supported fields: ['report', 'state', 'error_message'] + """ + data = _get_request_data() + if 'error_message' not in data: + data['error_message'] = "" + + return utils.make_json_response( + 200, + health_report_api.update_report( + cluster_id, name, **data + ) + ) + + +@app.route("/clusters//healthreports/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def get_health_report(cluster_id, name): + """Get health report by cluster id and name.""" + data = _get_request_data() + return utils.make_json_response( + 200, + health_report_api.get_health_report( + cluster_id, name, user=current_user, **data + ) + ) + + +@app.route("/clusters//hosts", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_cluster_hosts(cluster_id): + """Get cluster hosts.""" + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host(cluster_api.list_cluster_hosts( + cluster_id, user=current_user, **data + )) + ) + + +@app.route("/clusterhosts", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_clusterhosts(): + """Get cluster hosts.""" + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host(cluster_api.list_clusterhosts( + user=current_user, **data + )) + ) + + +@app.route("/clusters//hosts/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_cluster_host(cluster_id, host_id): + """Get clusterhost.""" + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host(cluster_api.get_cluster_host( + cluster_id, host_id, user=current_user, **data + )) + ) + + +@app.route("/clusterhosts/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_clusterhost(clusterhost_id): + """Get clusterhost.""" + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host(cluster_api.get_clusterhost( + clusterhost_id, user=current_user, **data + )) + ) + + +@app.route("/clusters//hosts", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_cluster_host(cluster_id): + """update cluster hosts. + + Must fields: ['machine_id'] + Optional fields: ['name', 'reinstall_os', 'roles'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.add_cluster_host(cluster_id, user=current_user, **data) + ) + + +@app.route( + '/clusters//hosts/', + methods=['PUT'] +) +@log_user_action +@login_required +@update_user_token +def update_cluster_host(cluster_id, host_id): + """Update cluster host. + + Supported fields: ['name', 'reinstall_os', 'roles'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_cluster_host( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@app.route( + '/clusterhosts/', + methods=['PUT'] +) +@log_user_action +@login_required +@update_user_token +def update_clusterhost(clusterhost_id): + """Update cluster host. + + Supported fields: ['name', 'reinstall_os', 'roles'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_clusterhost( + clusterhost_id, user=current_user, **data + ) + ) + + +@app.route( + '/clusters//hosts/', + methods=['PATCH'] +) +@log_user_action +@login_required +@update_user_token +def patch_cluster_host(cluster_id, host_id): + """Update cluster host. + + Supported fields: ['roles'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.patch_cluster_host( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@app.route( + '/clusterhosts/', + methods=['PATCH'] +) +@log_user_action +@login_required +@update_user_token +def patch_clusterhost(clusterhost_id): + """Update cluster host. + + Supported fields: ['roles'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.patch_clusterhost( + clusterhost_id, user=current_user, **data + ) + ) + + +@app.route( + '/clusters//hosts/', + methods=['DELETE'] +) +@log_user_action +@login_required +@update_user_token +def delete_cluster_host(cluster_id, host_id): + """Delete cluster host.""" + data = _get_request_data() + response = cluster_api.del_cluster_host( + cluster_id, host_id, user=current_user, **data + ) + if 'status' in response: + return utils.make_json_response( + 202, response + ) + else: + return utils.make_json_response( + 200, response + ) + + +@app.route( + '/clusterhosts/', + methods=['DELETE'] +) +@log_user_action +@login_required +@update_user_token +def delete_clusterhost(clusterhost_id): + """Delete cluster host.""" + data = _get_request_data() + response = cluster_api.del_clusterhost( + clusterhost_id, user=current_user, **data + ) + if 'status' in response: + return utils.make_json_response( + 202, response + ) + else: + return utils.make_json_response( + 200, response + ) + + +@app.route( + "/clusters//hosts//config", + methods=['GET'] +) +@log_user_action +@login_required +@update_user_token +def show_cluster_host_config(cluster_id, host_id): + """Get clusterhost config.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_cluster_host_config( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@app.route("/clusterhosts//config", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_clusterhost_config(clusterhost_id): + """Get clusterhost config.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_clusterhost_config( + clusterhost_id, user=current_user, **data + ) + ) + + +@app.route( + "/clusters//hosts//config", + methods=['PUT'] +) +@log_user_action +@login_required +@update_user_token +def update_cluster_host_config(cluster_id, host_id): + """update clusterhost config. + + Supported fields: ['os_config', package_config'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_cluster_host_config( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@app.route("/clusterhosts//config", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_clusterhost_config(clusterhost_id): + """update clusterhost config. + + Supported fields: ['os_config', 'package_config'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_clusterhost_config( + clusterhost_id, user=current_user, **data + ) + ) + + +@app.route( + "/clusters//hosts//config", + methods=['PATCH'] +) +@log_user_action +@login_required +@update_user_token +def patch_cluster_host_config(cluster_id, host_id): + """patch clusterhost config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.patch_cluster_host_config( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@app.route("/clusterhosts/", methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def patch_clusterhost_config(clusterhost_id): + """patch clusterhost config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.patch_clusterhost_config( + clusterhost_id, user=current_user, **data + ) + ) + + +@app.route( + "/clusters//hosts//config", + methods=['DELETE'] +) +@log_user_action +@login_required +@update_user_token +def delete_cluster_host_config(cluster_id, host_id): + """Delete clusterhost config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.del_clusterhost_config( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@app.route("/clusterhosts//config", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_clusterhost_config(clusterhost_id): + """Delete clusterhost config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.del_clusterhost_config( + clusterhost_id, user=current_user, **data + ) + ) + + +@app.route( + "/clusters//hosts//state", + methods=['GET'] +) +@log_user_action +@login_required +@update_user_token +def show_cluster_host_state(cluster_id, host_id): + """Get clusterhost state.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_cluster_host_state( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@app.route("/clusterhosts//state", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_clusterhost_state(clusterhost_id): + """Get clusterhost state.""" + data = _get_request_args() + return utils.make_json_response( + 200, + cluster_api.get_clusterhost_state( + clusterhost_id, user=current_user, **data + ) + ) + + +@app.route( + "/clusters//hosts//state", + methods=['PUT', 'POST'] +) +@log_user_action +@login_required +@update_user_token +def update_cluster_host_state(cluster_id, host_id): + """update clusterhost state. + + Supported fields: ['state', 'percentage', 'message', 'severity'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_clusterhost_state( + cluster_id, host_id, user=current_user, **data + ) + ) + + +@util.deprecated +@app.route( + "/clusters//hosts//state_internal", + methods=['PUT', 'POST'] +) +def update_cluster_host_state_internal(clustername, hostname): + """update clusterhost state. + + Supported fields: ['ready'] + """ + # TODO(xicheng): it should be merged into update_cluster_host_state. + # TODO(xicheng): the api is not login required and no user checking. + data = _get_request_data() + clusters = cluster_api.list_clusters(name=clustername) + if not clusters: + raise exception_handler.ItemNotFound( + 'no clusters found for clustername %s' % clustername + ) + cluster_id = clusters[0]['id'] + hosts = host_api.list_hosts(name=hostname) + if not hosts: + raise exception_handler.ItemNotFound( + 'no hosts found for hostname %s' % hostname + ) + host_id = hosts[0]['id'] + return utils.make_json_response( + 200, + cluster_api.update_clusterhost_state_internal( + cluster_id, host_id, **data + ) + ) + + +@app.route( + "/clusterhosts//state", + methods=['PUT', 'POST'] +) +@log_user_action +@login_required +@update_user_token +def update_clusterhost_state(clusterhost_id): + """update clusterhost state. + + Supported fields: ['state', 'percentage', 'message', 'severity'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + cluster_api.update_clusterhost_state( + clusterhost_id, user=current_user, **data + ) + ) + + +@util.deprecated +@app.route( + "/clusterhosts//state_internal", + methods=['PUT', 'POST'] +) +def update_clusterhost_state_internal(clusterhost_name): + """update clusterhost state. + + Supported fields: ['ready'] + """ + data = _get_request_data() + clusterhosts = cluster_api.list_clusterhosts() + clusterhost_id = None + for clusterhost in clusterhosts: + if clusterhost['name'] == clusterhost_name: + clusterhost_id = clusterhost['clusterhost_id'] + break + if not clusterhost_id: + raise exception_handler.ItemNotFound( + 'no clusterhost found for clusterhost_name %s' % ( + clusterhost_name + ) + ) + return utils.make_json_response( + 200, + cluster_api.update_clusterhost_state_internal( + clusterhost_id, **data + ) + ) + + +@app.route("/hosts", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_hosts(): + """List hosts. + + Supported fields: ['name', 'os_name', 'owner', 'mac'] + """ + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host(host_api.list_hosts( + user=current_user, **data + )) + ) + + +@app.route("/hosts/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_host(host_id): + """Get host.""" + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host(host_api.get_host( + host_id, user=current_user, **data + )) + ) + + +@app.route("/machines-hosts", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_machines_or_hosts(): + """Get list of machine of host if the host exists. + + Supported filters: [ + 'mac', 'tag', 'location', 'os_name', 'os_id' + ] + """ + data = _get_request_args(os_id=_int_converter) + _filter_machine_tag(data) + _filter_machine_location(data) + _filter_general(data, 'os_name') + _filter_general(data, 'os_id') + return utils.make_json_response( + 200, + _reformat_host(host_api.list_machines_or_hosts( + user=current_user, **data + )) + ) + + +@app.route("/machines-hosts/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_machine_or_host(host_id): + """Get host.""" + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host(host_api.get_machine_or_host( + host_id, user=current_user, **data + )) + ) + + +@app.route("/hosts/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_host(host_id): + """update host. + + Supported fields: ['name', 'reinstall_os'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.update_host( + host_id, user=current_user, **data + ) + ) + + +@app.route("/hosts", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_hosts(): + """update hosts. + + update a list of host as dict each may contains following keys: [ + 'name', 'reinstall_os' + ] + """ + data = _get_request_data_as_list() + return utils.make_json_response( + 200, + host_api.update_hosts( + data, user=current_user, + ) + ) + + +@app.route("/hosts/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_host(host_id): + """Delete host.""" + data = _get_request_data() + response = host_api.del_host( + host_id, user=current_user, **data + ) + if 'status' in response: + return utils.make_json_response( + 202, response + ) + else: + return utils.make_json_response( + 200, response + ) + + +@app.route("/hosts//clusters", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def get_host_clusters(host_id): + """Get host clusters.""" + data = _get_request_args() + return utils.make_json_response( + 200, + host_api.get_host_clusters( + host_id, user=current_user, **data + ) + ) + + +@app.route("/hosts//config", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_host_config(host_id): + """Get host config.""" + data = _get_request_args() + return utils.make_json_response( + 200, + host_api.get_host_config( + host_id, user=current_user, **data + ) + ) + + +@app.route("/hosts//config", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_host_config(host_id): + """update host config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.update_host_config(host_id, user=current_user, **data) + ) + + +@app.route("/hosts/", methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def patch_host_config(host_id): + """patch host config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.patch_host_config(host_id, user=current_user, **data) + ) + + +@app.route("/hosts//config", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_host_config(host_id): + """Delete host config.""" + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.del_host_config( + host_id, user=current_user, **data + ) + ) + + +@app.route("/hosts//networks", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_host_networks(host_id): + """list host networks. + + Supported filters: [ + 'interface', 'ip', 'is_mgmt', 'is_promiscuous' + ] + """ + data = _get_request_args() + return utils.make_json_response( + 200, + _reformat_host_networks( + host_api.list_host_networks( + host_id, user=current_user, **data + ) + ) + ) + + +@app.route("/host/networks", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def list_hostnetworks(): + """list host networks. + + Supported filters: [ + 'interface', 'ip', 'is_mgmt', 'is_promiscuous' + ] + """ + data = _get_request_args( + is_mgmt=_bool_converter, + is_promiscuous=_bool_converter + ) + return utils.make_json_response( + 200, + _reformat_host_networks( + host_api.list_hostnetworks(user=current_user, **data) + ) + ) + + +@app.route( + "/hosts//networks/", + methods=['GET'] +) +@log_user_action +@login_required +@update_user_token +def show_host_network(host_id, host_network_id): + """Get host network.""" + data = _get_request_args() + return utils.make_json_response( + 200, + host_api.get_host_network( + host_id, host_network_id, user=current_user, **data + ) + ) + + +@app.route("/host/networks/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_hostnetwork(host_network_id): + """Get host network.""" + data = _get_request_args() + return utils.make_json_response( + 200, + host_api.get_hostnetwork( + host_network_id, user=current_user, **data + ) + ) + + +@app.route("/hosts//networks", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def add_host_network(host_id): + """add host network. + + Must fields: ['interface', 'ip', 'subnet_id'] + Optional fields: ['is_mgmt', 'is_promiscuous'] + """ + data = _get_request_data() + return utils.make_json_response( + 200, host_api.add_host_network(host_id, user=current_user, **data) + ) + + +@app.route("/hosts/networks", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_host_networks(): + """add host networks. + + update a list of host network each may contain [ + 'interface', 'ip', 'subnet_id', 'is_mgmt', 'is_promiscuous' + ] + """ + data = _get_request_data_as_list() + return utils.make_json_response( + 200, host_api.add_host_networks( + data=data, user=current_user,) + ) + + +@app.route( + "/hosts//networks/", + methods=['PUT'] +) +@log_user_action +@login_required +@update_user_token +def update_host_network(host_id, host_network_id): + """update host network. + + supported fields: [ + 'interface', 'ip', 'subnet_id', 'subnet', 'is_mgmt', + 'is_promiscuous' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.update_host_network( + host_id, host_network_id, user=current_user, **data + ) + ) + + +@app.route("/host-networks/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def update_hostnetwork(host_network_id): + """update host network. + + supported fields: [ + 'interface', 'ip', 'subnet_id', 'subnet', 'is_mgmt', + 'is_promiscuous' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.update_hostnetwork( + host_network_id, user=current_user, **data + ) + ) + + +@app.route( + "/hosts//networks/", + methods=['DELETE'] +) +@log_user_action +@login_required +@update_user_token +def delete_host_network(host_id, host_network_id): + """Delete host network.""" + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.del_host_network( + host_id, host_network_id, user=current_user, **data + ) + ) + + +@app.route("/host-networks/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def delete_hostnetwork(host_network_id): + """Delete host network.""" + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.del_hostnetwork( + host_network_id, user=current_user, **data + ) + ) + + +@app.route("/hosts//state", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def show_host_state(host_id): + """Get host state.""" + data = _get_request_args() + return utils.make_json_response( + 200, + host_api.get_host_state( + host_id, user=current_user, **data + ) + ) + + +@app.route("/hosts//state", methods=['PUT', 'POST']) +@log_user_action +@login_required +@update_user_token +def update_host_state(host_id): + """update host state. + + Supported fields: [ + 'state', 'percentage', 'message', 'severity' + ] + """ + data = _get_request_data() + return utils.make_json_response( + 200, + host_api.update_host_state( + host_id, user=current_user, **data + ) + ) + + +@app.route("/hosts//state_internal", methods=['PUT', 'POST']) +def update_host_state_internal(hostname): + """update host state. + + Supported fields: ['ready'] + """ + data = _get_request_data() +# host_id = int(host_id) +# hosts = host_api.list_hosts(id=host_id) + hosts = host_api.list_hosts(name=hostname) + if not hosts: + raise exception_handler.ItemNotFound( + 'no hosts found for hostname %s' % hostname + ) + host_id = hosts[0]['id'] + return utils.make_json_response( + 200, + host_api.update_host_state_internal( + host_id, **data + ) + ) + + +@app.route("/hosts//action", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def take_host_action(host_id): + """take host action. + + Supported actions: [ + 'poweron', 'poweroff', 'reset' + ] + """ + data = _get_request_data() + poweron_func = _wrap_response( + functools.partial( + host_api.poweron_host, host_id, user=current_user, + ), + 202 + ) + poweroff_func = _wrap_response( + functools.partial( + host_api.poweroff_host, host_id, user=current_user, + ), + 202 + ) + reset_func = _wrap_response( + functools.partial( + host_api.reset_host, host_id, user=current_user, + ) + ) + return _group_data_action( + data, + poweron=poweron_func, + poweroff=poweroff_func, + reset=reset_func, + ) + + +def _get_headers(*keys): + """Get proxied request headers.""" + headers = {} + for key in keys: + if key in request.headers: + headers[key] = request.headers[key] + return headers + + +def _get_response_json(response): + """Get proxies request json formatted response.""" + try: + return response.json() + except ValueError: + return response.text + + +@app.route("/proxy/", methods=['GET']) +@log_user_action +@login_required +@update_user_token +def proxy_get(url): + """proxy url.""" + headers = _get_headers( + 'Content-Type', 'Accept-Encoding', + 'Content-Encoding', 'Accept', 'User-Agent', + 'Content-MD5', 'Transfer-Encoding', app.config['AUTH_HEADER_NAME'], + 'Cookie' + ) + response = requests.get( + '%s/%s' % (setting.PROXY_URL_PREFIX, url), + params=_get_request_args(), + headers=headers, + stream=True + ) + logging.debug( + 'proxy %s response: %s', + url, response.text + ) + return utils.make_json_response( + response.status_code, _get_response_json(response) + ) + + +@app.route("/proxy/", methods=['POST']) +@log_user_action +@login_required +@update_user_token +def proxy_post(url): + """proxy url.""" + headers = _get_headers( + 'Content-Type', 'Accept-Encoding', + 'Content-Encoding', 'Accept', 'User-Agent', + 'Content-MD5', 'Transfer-Encoding', + 'Cookie' + ) + response = requests.post( + '%s/%s' % (setting.PROXY_URL_PREFIX, url), + data=request.data, + headers=headers + ) + logging.debug( + 'proxy %s response: %s', + url, response.text + ) + return utils.make_json_response( + response.status_code, _get_response_json(response) + ) + + +@app.route("/proxy/", methods=['PUT']) +@log_user_action +@login_required +@update_user_token +def proxy_put(url): + """proxy url.""" + headers = _get_headers( + 'Content-Type', 'Accept-Encoding', + 'Content-Encoding', 'Accept', 'User-Agent', + 'Content-MD5', 'Transfer-Encoding', + 'Cookie' + ) + response = requests.put( + '%s/%s' % (setting.PROXY_URL_PREFIX, url), + data=request.data, + headers=headers + ) + logging.debug( + 'proxy %s response: %s', + url, response.text + ) + return utils.make_json_response( + response.status_code, _get_response_json(response) + ) + + +@app.route("/proxy/", methods=['PATCH']) +@log_user_action +@login_required +@update_user_token +def proxy_patch(url): + """proxy url.""" + headers = _get_headers( + 'Content-Type', 'Accept-Encoding', + 'Content-Encoding', 'Accept', 'User-Agent', + 'Content-MD5', 'Transfer-Encoding', + 'Cookie' + ) + response = requests.patch( + '%s/%s' % (setting.PROXY_URL_PREFIX, url), + data=request.data, + headers=headers + ) + logging.debug( + 'proxy %s response: %s', + url, response.text + ) + return utils.make_json_response( + response.status_code, _get_response_json(response) + ) + + +@app.route("/proxy/", methods=['DELETE']) +@log_user_action +@login_required +@update_user_token +def proxy_delete(url): + """proxy url.""" + headers = _get_headers( + 'Content-Type', 'Accept-Encoding', + 'Content-Encoding', 'Accept', 'User-Agent', + 'Content-MD5', 'Transfer-Encoding', + 'Cookie' + ) + response = requests.delete( + '%s/%s' % (setting.PROXY_URL_PREFIX, url), + headers=headers + ) + logging.debug( + 'proxy %s response: %s', + url, response.text + ) + return utils.make_json_response( + response.status_code, _get_response_json(response) + ) + + +def init(): + logging.info('init flask') + database.init() + adapter_api.load_adapters() + metadata_api.load_metadatas() + adapter_api.load_flavors() + + +if __name__ == '__main__': + flags.init() + logsetting.init() + init() + app.run(host='0.0.0.0') diff --git a/compass-deck/api/api.raml b/compass-deck/api/api.raml new file mode 100644 index 0000000..6855b57 --- /dev/null +++ b/compass-deck/api/api.raml @@ -0,0 +1,4027 @@ +#%RAML 0.8 +title: Compass +version: v1 +baseUri: http://10.145.89.151/api +mediaType: application/json + + +/permissions: + get: + body: + application/json: + responses: + 200: + body: + application/json: + example: | + [ + { + "alias": "list permissions", + "description": "list all permissions", + "id": 1, + "name": "list_permissions" + }, + ] + description: List all permissions + headers: + X-Auth-Header: + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{permission_id}: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "alias": "list permissions", + "description": "list all permissions", + "id": 1, + "name": "list_permissions" + } + ] + 404: + body: + application/json: + example: | + { + message: "Cannot find the record in table Permission: {'id': ''}" + } + description: List a specific permission info + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/users: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "id": 1, + "email": "someuser@email.com", + "first_name": "", + "last_name": "", + "is_admin": false, + "active": true, + "created_at": "--timestamp---", + "last_login_at": "--timestamp---" + }, + ] + + description: Lists information for all users + headers: + X-Auth-Header: + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + queryParameters: + email: + is_admin: + active: + post: + body: + application/json: + schema: | + { + "email": "admin@someemail.com", + "password": "admin", + "firstname": "First", + "lastname": "Last" + } + responses: + 201: + body: + application/json: + example: | + { + "id": 3, + "email": "user3@someemail.com", + "first_name": "", + "last_name": "", + "is_admin": false, + "active": true, + "created_at": "--timestamp---", + "last_login_at": "--timestamp---" + } + 400: + body: + application/json: + example: | + { + "bad request" + } + 403: + body: + application/json: + example: | + { + "forbidden" + } + 409: + body: + application/json: + example: | + { + "message": "The user already exists!" + } + description: Creates a user(admin only) + headers: + X-Auth-Header: + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{user_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "id": 1, + "email": "someuser@email.com", + "first_name": "", + "last_name": "", + "is_admin": false, + "active": true, + "created_at": "2014-03-25 12:00:00", + "last_login_at": "2014-03-25 12:05:00" + } + 404: + body: + application/json: + example: | + { + "message": "The user with id 'some--id--' cannot be found!" + } + description: Lists information for a specific user + headers: + X-Auth-Header: + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + {"password": 123} + responses: + 201: + body: + application/json: + example: | + { + "id": 3, + "email": "user3@someemail.com", + "first_name": "", + "last_name": "", + "is_admin": false, + "active": true + } + 409: + body: + application/json: + example: | + { + "message": "The user with id 'some--id--' cannot be found!" + } + description: Updates user’s information + headers: + X-Auth-Header: + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "id": 3, + "email": "user3@someemail.com", + "first_name": "", + "last_name": "", + "is_admin": false, + "active": true + } + 409: + body: + application/json: + example: | + { + "message": "The user cannot be found!" + } + description: Deletes a user(admin only) + headers: + X-Auth-Header: + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /permissions: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "created_at": "2014-10-17 16:28:21", + "user_id": 1, + "description": "list all permissions", + "permission_id": 1, + "updated_at": "2014-10-17 16:28:21", + "alias": "list permissions", + "id": 1, + "name": "list_permissions" + } + ] + 409: + body: + application/json: + example: | + { + "type": "itemNotFound", + "message": "The user with id 'some--id--' cannot be found!" + } + description: Lists permissions for a specified user + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /action: + post: + body: + application/json: + schema: | + { + "add_permissions": [1,2,3], + "remove_permissions": [1], + "set_permissions": [1], + "disable_user": [1], + "enable_user": [1] + } + responses: + 200: + body: + application/json: + example: | + Add permission: + + [ + { + "created_at": "2014-10-17 16:28:21", + "user_id": 1, + "description": "list all permissions", + "permission_id": 1, + "updated_at": "2014-10-17 16:28:21", + "alias": "list permissions", + "id": 1, + "name": "list_permissions" + } + ] + + Remove permission: + + [ + { + "created_at": "2014-10-17 16:28:21", + "user_id": 1, + "description": "list all permissions", + "permission_id": 1, + "updated_at": "2014-10-17 16:28:21", + "alias": "list permissions", + "id": 1, + "name": "list_permissions" + } + ] + + Set Permission: + + [ + { + "created_at": "2014-10-17 16:28:21", + "user_id": 1, + "description": "list all permissions", + "permission_id": 1, + "updated_at": "2014-10-17 16:28:21", + "alias": "list permissions", + "id": 1, + "name": "list_permissions" + } + ] + + Enable user: + + { + "created_at": "2014-10-17 16:28:21", + "updated_at": "2014-10-17 16:28:21", + "email": "admin@huawei.com", + "is_admin": true, + "active": true, + "id": 1 + } + + Disable user: + + { + "created_at": "2014-10-17 16:28:21", + "updated_at": "2014-10-17 16:28:21", + "email": "admin@huawei.com", + "is_admin": true, + "active": true, + "id": 1 + } + 409: + body: + application/json: + example: | + { + "type": "itemNotFound", + "message": "The user cannot be found!" + } + description: Adds/Removes permissions, Enable/Disable a user (admin only) + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /token: + post: + body: + application/json: + schema: | + { + "email": "admin@huawei.com", + "password": "admin" + } + responses: + 200: + body: + application/json: + example: | + { + "expire_timestamp": "2014-10-06 13:25:23", + "token": "$1$c1ZWGYEn$WTg57cnP4pEwd9JMJ7beS/", + "user_id": 1, + "id": 3 + } + 409: + body: + application/json: + example: | + { + "type": "unauthorized", + "message": "Either email or password is wrong!" + } + description: Authenticates and generates a token + /login: + post: + body: + application/json: + schema: | + { + "email": "admin@huawei.com", + "password": "admin" + } + responses: + 200: + body: + application/json: + example: | + { + "expire_timestamp": "2014-10-06 13:25:23", + "token": "$1$c1ZWGYEn$WTg57cnP4pEwd9JMJ7beS/", + "user_id": 1, + "id": 3 + } + 401: + body: + application/json: + example: | + { + "type": "unauthorized", + "message": "Either email or password is wrong!" + } + 403: + body: + application/json: + example: | + { + "type": "userDisabled", + "message”: "User is disabled !" + } + description: Login + /logout: + post: + responses: + 200: + body: + application/json: + example: | + [ + { + "expire_timestamp": "2014-10-17 18:30:29", + "token": "$1$AFqIS5Kn$1ASgOkPv.G1a7pkRRHKY.0", + "user_id": 1, + "id": 1 + } + ] + 401: + body: + application/json: + example: | + { + "message": "invalid user token: $1$AFqIS5Kn$1ASgOkPv.G1a7pkRRHKY.0", + } + description: Logout + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/switches: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "ip": "172.29.8.40", + "created_at": "2014-10-17 17:28:06", + "updated_at": "2014-10-17 17:28:06", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "public" + }, + "id": 2 + } + ] + description: Lists switches + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + post: + body: + application/json: + schema: | + { + "ip": "172.29.8.40", + "credentials": + { + "version": "2c", + "community": "public" + } + } + responses: + 200: + body: + application/json: + example: | + { + "ip": "172.29.8.40", + "created_at": "2014-10-17 17:28:06", + "updated_at": "2014-10-17 17:28:06", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "public" + }, + "id": 2 + } + 409: + body: + application/json: + example: | + { + "message": "IP address '192.168.1.1' already exists" + } + description: Creates a switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{switch_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "ip": "172.29.8.40", + "created_at": "2014-10-17 17:28:06", + "updated_at": "2014-10-17 17:28:06", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "public" + }, + "id": 2 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot find the switch which id is '1'." + } + description: Lists a switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "ip": "172.29.8.40", + "credentials": + { + "version": "2c", + "community": "private" + } + } + responses: + 200: + body: + application/json: + example: | + { + "ip": "172.29.8.40", + "created_at": "2014-10-17 17:28:06", + "updated_at": "2014-10-17 17:28:06", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "private" + }, + "id": 2 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot update the switch which id is '1'! The switch does not exists." + } + description: Set the switch properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "ip": "172.29.8.40", + "credentials": + { + "version": "3", + "community": "public" + } + } + responses: + 200: + body: + application/json: + example: | + { + "ip": "172.29.8.40", + "created_at": "2014-10-17 17:28:06", + "updated_at": "2014-10-17 17:28:06", + "state": "initialized", + "filters": "", + "credentials": { + "version": "3", + "community": "public" + }, + "id": 2 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot update the switch which id is '1'! The switch does not exists." + } + description: Updates the switch properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "ip": "172.29.8.41", + "created_at": "2014-10-17 17:45:17", + "updated_at": "2014-10-17 17:45:17", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "public" + }, + "id": 3 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot find the record in table Switch: {'id': 4}" + } + description: Delete switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /machines: + get: + responses: + 200: + body: + application/json: + example: | + Get: + [ + { + "vlans": [], + "updated_at": "2014-10-17 18:02:21", + "created_at": "2014-10-17 18:02:21", + "switch_id": 3, + "id": 1, + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": {}, + "switch_ip": "172.29.8.41", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "10", + "switch_machine_id": 204 + } + ] + queryParameters: + port: + portStart: + portEnd: + portRange: + PortPrefix: + PortSuffix: + vlans: + mac: + tag: + location: + description: Lists machines for a specified switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + post: + body: + application/json: + schema: | + { + "mac": "28:6e:d4:46:c4:25", + "port": "1", + "vlans": "88", + "ipmi_credentials": { + "ip": "1.2.3.4", + "username": "test", + "password": "test" + }, + "tag": "tag", + "location": { + "column": "1", + "row": "1", + "unit": "1" + } + } + responses: + 200: + body: + application/json: + example: | + { + "id": 1, + "mac": "28:6e:d4:47:c8:6c", + "vlan": 1, + "port": "10" + } + 404: + body: + application/json: + example: | + { + "message": "The switch does not exists." + } + description: Manually add a machine + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /machines: + post: + body: + application/json: + schema: | + { + "mac": "28:6e:d4:46:c4:25", + "port": "1", + "vlans": "88" + } + responses: + 200: + body: + application/json: + example: | + { + "duplicate_switches_machines": [ + { + "mac": "a1:b2:c3:d4:e1:f6", + "port": "101" + } + ], + "switches_machines": [ + { + "vlans": [], + "updated_at": "2015-05-07 10:55:12", + "created_at": "2015-05-07 10:55:12", + "switch_id": 2, + "id": 1, + "mac": "70:7b:e8:e2:72:21", + "tag": {}, + "location": {}, + "switch_ip": "10.145.8.10", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "204", + "switch_machine_id": 1 + }, + { + "vlans": [], + "updated_at": "2015-05-07 10:55:12", + "created_at": "2015-05-07 10:55:12", + "switch_id": 2, + "id": 2, + "mac": "a1:b2:c3:d4:e1:f6", + "tag": {}, + "location": {}, + "switch_ip": "10.145.8.10", + "ipmi_credentials": {}, + "machine_id": 2, + "port": "101", + "switch_machine_id": 2 + }, + { + "vlans": [], + "updated_at": "2015-05-07 10:55:12", + "created_at": "2015-05-07 10:55:12", + "switch_id": 3, + "id": 3, + "mac": "a1:b2:c3:d4:e5:f9", + "tag": {}, + "location": {}, + "switch_ip": "172.29.8.40", + "ipmi_credentials": {}, + "machine_id": 3, + "port": "121", + "switch_machine_id": 3 + } + ], + "fail_switches_machines": [ + { + "mac": "a1:b5:c3:d4:e5:f9", + "port": "131" + }, + { + "mac": "a1:b2:c3:d4:e1:f6", + "port": "13" + } + ] + } + description: Batch switch machines. If the machine is connected to other switch or switch does not exist, it will be added to fail_switches_machines and return. If machine is already existed, it will be added to duplicate_switches_machines. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + + /{id}/machines/{machine_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "vlans": [ + 88 + ], + "updated_at": "2014-10-17 17:40:13", + "created_at": "2014-10-17 17:40:13", + "switch_id": 2, + "id": 1, + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": {}, + "switch_ip": "172.29.8.40", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "7", + "switch_machine_id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot find the record in table SwitchMachine: {'machine_id': 1000, 'switch_id': 2}" + } + description: Get machine of a specified switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "port": "80", + "vlans": "88", + "pmi_credentials": "pmi_credentials here", + "tag": "tag here", + "location": + {"building": "E5"} + } + responses: + 200: + body: + application/json: + example: | + { + "vlans": [ + 88 + ], + "updated_at": "2014-10-17 17:40:13", + "created_at": "2014-10-17 17:40:13", + "switch_id": 2, + "id": 1, + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": { + "building": "E5" + }, + "switch_ip": "172.29.8.40", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "7", + "switch_machine_id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot find the record in table SwitchMachine: {'machine_id': 1000, 'switch_id': 2}" + } + description: set machine property of a specified switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "port": "80", + "vlans": "88", + "pmi_credentials": "pmi_credentials here", + "tag": "tag here", + "location": + {"city": "Beijing"} + } + responses: + 200: + body: + application/json: + example: | + { + "vlans": [ + 88 + ], + "updated_at": "2014-10-17 17:40:13", + "created_at": "2014-10-17 17:40:13", + "switch_id": 2, + "id": 1, + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": { + "building": "E5", + "city": "beijing" + }, + "switch_ip": "172.29.8.40", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "7", + "switch_machine_id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot find the record in table SwitchMachine: {'machine_id': 1000, 'switch_id': 2}" + } + description: update machine property of a specified switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "vlans": [ + 88 + ], + "updated_at": "2014-10-17 17:40:13", + "created_at": "2014-10-17 17:40:13", + "switch_id": 2, + "id": 1, + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": { + "building": "E5", + "city": "beijing" + }, + "switch_ip": "172.29.8.40", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "7", + "switch_machine_id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot find the record in table SwitchMachine: {'machine_id': 1000, 'switch_id': 2}" + } + description: Delete a machine from a switch + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{switch_id}/action: + post: + body: + application/json: + schema: | + { + "find_machines": 1, + "add_macheins": [{"machine_id":1,"port":"10"}], + "rermove_machines": 1, + "set_machines": [{"machine_id": 1, "port": "10"}] + } + responses: + 202: + body: + application/json: + example: | + find_machines: + { + "status": "action {'find_machines': None} sent", + "details": {} + } + 200: + body: + application/json: + example: | + add_machines: + [ + { + "vlans": [], + "updated_at": "2014-10-17 17:56:44", + "created_at": "2014-10-17 17:56:44", + "switch_id": 3, + "id": 1, + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": {}, + "switch_ip": "172.29.8.41", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "10", + "switch_machine_id": 203 + } + ] + + remove_machines: + [] + set_machines: + [ + { + "vlans": [], + "updated_at": "2014-10-17 17:56:44", + "created_at": "2014-10-17 17:56:44", + "switch_id": 3, + "id": 1, + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": {}, + "switch_ip": "172.29.8.41", + "ipmi_credentials": {}, + "machine_id": 1, + "port": "10", + "switch_machine_id": 203 + } + ] + 404: + body: + application/json: + example: | + { + "message": "Cannot update the switch which id is '1'! The switch does not exists." + } + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/switchbatch: + post: + body: + application/json: + schema: | + [{ + "switch_ip": "127.0.0.1": + "credentials":{ + "version": "2c", + "community": "public" + },{ + "switch_ip": "127.0.0.2" + }] + responses: + 200: + body: + application/json: + example: | + { + "switches": [ + { + "vendor": "Huawei", + "ip": "10.145.8.10", + "created_at": "2015-05-04 16:13:34", + "updated_at": "2015-05-04 16:13:34", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "public" + }, + "id": 2 + }, + { + "ip": "172.29.8.40", + "created_at": "2015-05-04 16:13:34", + "updated_at": "2015-05-04 16:13:34", + "state": "initialized", + "filters": "", + "credentials": {}, + "id": 3 + } + ], + "fail_switches": [ + { + "ip": "172.29.8.40" + } + ] + } + description: Batch switches. If switch ip already existed, switch data will be added in fail_switches list and return. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/machines: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "created_at": "2014-10-17 17:40:13", + "updated_at": "2014-10-17 23:22:53", + "switches": [], + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": { + "building": "E5", + "city": "beijing" + }, + "ipmi_credentials": {}, + "id": 1 + }, + ] + queryParameters: + mac: + tag: + location: + description: Lists machines + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{machine_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "created_at": "2014-10-17 17:40:13", + "updated_at": "2014-10-17 23:22:53", + "switches": [], + "mac": "28:6e:d4:46:c4:25", + "tag": {}, + "location": { + "building": "E5", + "city": "beijing" + }, + "ipmi_credentials": {}, + "id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "The machine witch ID '$machine_id' cannot be found!" + } + description: Lists machines of a specific machine + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "ipmi_credentials": { + "builder": "huawei" + } + } + responses: + 200: + body: + application/json: + example: | + { + "created_at": "2014-10-17 17:40:13", + "updated_at": "2014-10-17 23:58:46", + "switches": [], + "mac": "28:6e:d4:46:c4:25", + "tag": { + "builder": "huawei" + }, + "location": { + "building": "E5", + "city": "beijing" + }, + "ipmi_credentials": {}, + "id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "The machine witch ID “$machine_id” cannot be found!" + } + description: set machine properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "ipmi_credentials": { + "builder": "huawei" + }, + "tag": { + "type": "ES200" + } + } + responses: + 200: + body: + application/json: + example: | + { + "created_at": "2014-10-17 17:40:13", + "updated_at": "2014-10-18 00:03:12", + "switches": [], + "mac": "28:6e:d4:46:c4:25", + "tag": { + "type": "ES200" + }, + "location": { + "building": "E5", + "city": "beijing" + }, + "ipmi_credentials": {}, + "id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "The machine witch ID '$machine_id' cannot be found!" + } + description: updatge machine properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "created_at": "2014-10-17 17:40:13", + "updated_at": "2014-10-18 00:03:12", + "switches": [], + "mac": "28:6e:d4:46:c4:25", + "tag": { + "type": "ES200" + }, + "location": { + "building": "E5", + "city": "beijing" + }, + "ipmi_credentials": {}, + "id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "The machine witch ID '$machine_id' cannot be found!" + } + description: Delete a machine (admin only) + /action: + post: + body: + application/json: + schema: | + { + "tag": {"builder": "huawei"}, + "poweron": "true", + "poweroff": "true", + "reset": "true" + } + responses: + 200: + body: + application/json: + example: | + tag example: + + { + "created_at": "2014-10-17 17:40:13", + "updated_at": "2014-10-18 00:10:58", + "id": 2, + "switches": [ + { + "switch_ip": "172.29.8.40", + "vlans": [ + 88 + ], + "port": "4" + } + ], + "mac": "00:0c:29:2b:c9:d4", + "tag": { + "builder": "huawei" + }, + "location": {}, + "switch_ip": "172.29.8.40", + "ipmi_credentials": {}, + "vlans": [ + 88 + ], + "port": "4" + } + + poweron/ poweroff / reset is null example: + + { + "status": "poweron 00:0c:29:2b:c9:d4 action sent", + } + 404: + body: + application/json: + example: | + { + "message": "The machine witch ID '$machine_id' cannot be found!" + } + 400: + body: + application/json: + example: | + { + "message": "The machine haven't set IPMI info!" + } + description: machine actions + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/flavors: + /{flavor_id}/metadata: + get: + responses: + 200: + body: + application/json: + example: | + { + "flavor_config": { + "neutron_config": {...}, + "security": {...}, + "ha_proxy": {...}, + "network_mapping": {...} + + } + } + 404: + body: + application/json: + example: | + {message: "flavor does not exist"} + description: List specific flavor metadata. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{flavor_id}/ui_metadata: + get: + responses: + 200: + body: + application/json: + example: | + { + "flavor_config": + { + "category": "service_credentials", + "modifiable_data": [ + "username", + "password", + ] + "table_display_header": [ + "Service", + "UserName", + "Password", + "Action", + ] + "accordion_heading": "OpenStack Database and Queue Credentials", + "action”: true, + "data_structure": "table" + }, + {...}, + {...} + } + 404: + body: + application/json: + example: | + {message: "flavor does not exist"} + description: List specific flavor ui metadata. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/adapters: + get: + responses: + 200: + body: + application/json: + example: | + [{ + "flavors": [ + { + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "role_id": 35, + "flavor_id": 4, + "optional": true, + "id": 35, + "name": "allinone-compute" + } + ], + "display_name": "All-In-One", + "id": 4, + "template": "allinone.tmpl", + "name": "allinone" + }, + ], + "package_installer": { + "id": 1, + "alias": "chef_installer", + "name": "chef_installer", + "settings": { + "chef_server_ip": "10.145.88.211", + "client_name": "", + "chef_server_dns": "compass", + "databags": [], + "chef_url": "https://10.145.88.211", + "key_dir": "" + } + }, + "name": "openstack_icehouse", + "os_installer": { + "id": 1, + "alias": "cobbler", + "name": "cobbler", + "settings": { + "credentials": { + "username": "cobbler", + "password": "cobbler" + }, + "cobbler_url": "http://10.145.88.211/cobbler_api" + } + }, + "supported_oses": [ + { + "os_id": 1, + "id": 1, + "name": "Ubuntu-12.04-x86_64" + }, + { + "os_id": 2, + "id": 2, + "name": "CentOS-6.5-x86_64" + } + ], + "display_name": "OpenStack Icehouse", + "id": 3 + }] + queryParameters: + name: + description: Lists information for all adapters + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "id" : 1, + "name": "openstack", + "display": "OpenStack", + "os_installer": "cobbler", + "package_installer": "chef", + "roles": [ { "display_name": "compute", + "name": "os-compute-worker" + }, + { "display_name": "controller", + "name": "os-controller" + }, + { "display_name": "network", + "name": "os-network" + }, + { "display_name": "storage", + "name": "os-block-storage-worker" + ], + "compatible_os": [ + { + "name": "CentOs", + "os_id": 1 + }, + { + "name": "Ubuntu", + "os_id": 2 + } + ] + } + 404: + body: + application/json: + example: | + { + "message": "The adapter with id 'some_id' cannot be found!" + } + description: Lists information for a specified adapter + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /oses/{os_id}/metadata: + get: + responses: + 200: + body: + application/json: + example: | + { + "package_config": { + "security": { + "_self": { + "mapping_to": "", + "description": null, + "required_in_whole_config": true, + "display_type": null, + "js_validator": null, + "default_value": null, + "field_type": "dict", + "name": "security", + "required_in_options": false, + "is_required": false, + "options": null + }, + }, + "os_config": { + "server_credentials": { + "_self": { + "mapping_to": "server_credentials", + "description": null, + "required_in_whole_config": true, + "display_type": null, + "js_validator": null, + "default_value": null, + "field_type": "dict", + "name": "server_credentials", + "required_in_options": false, + "is_required": false, + "options": null + }, + "username": { + "_self": { + "mapping_to": "username", + "description": "username", + "required_in_whole_config": false, + "display_type": "text", + "js_validator": null, + "default_value": "root", + "field_type": "basestring", + "name": "username", + "required_in_options": false, + "is_required": true, + "options": null + } + }, + }, + }, + } + 404: + body: + application/json: + example: | + { + "message": "The adapter with id 'some_id' cannot be found!" + } + description: Lists config formats for a specified adapter and os + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /oses/{os_id}/ui_metadata: + get: + responses: + 200: + body: + application/json: + example: | + { + "os_global_config": [ + { + "title": "Server Credentials", + "data": [ + { + "default_value": "root", + "display_name": "User name", + "name": "username", + "display_type": "text", + "is_required": "true", + "placeholder": "Username", + "order": 1 + }, + { + "display_name": "Confirm Password", + "name": "confirmPassword", + "datamatch": "password", + "display_type": "password", + "is_required": "true", + "placeholder": "Confirm Password", + "order": 3 + }, + { + "display_name": "Password", + "name": "password", + "display_type": "password", + "is_required": "true", + "placeholder": "Password", + "order": 2 + }], + "order": 2, + "name": "server_credentials" + }, + } + }] + } + 404: + body: + application/json: + example: | + { + "message": "os does not exist" + } + description: List specified os ui metadata. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + +/subnets: + get: + responses: + 200: + body: + application/json: + example: | + [{ + "updated_at": "2014-10-18 21:24:46", + "subnet": "10.145.88.0/23", + "created_at": "2014-10-18 21:24:46", + "id": 1, + "name": "10.145.88.0/23" + }] + description: Gets all subnetworks information + post: + body: + application/json: + schema: | + { + "subnet": "10.172.20.0/24", + "name": "test_subnet" + } + responses: + 200: + body: + application/json: + example: | + { + "updated_at": "2014-10-18 21:24:46", + "subnet": "10.145.88.0/23", + "created_at": "2014-10-18 21:24:46", + "id": 1, + "name": "10.145.88.0/23" + } + 400: + body: + application/json: + example: | + { + "message": "Keyword '$somekey' cannot be recognized!" + } + 409: + body: + application/json: + example: | + { + "message": "Subnet already exists!" + } + description: Creates one subnetwork + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{subnet_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "updated_at": "2014-10-18 21:24:46", + "subnet": "10.145.88.0/23", + "created_at": "2014-10-18 21:24:46", + "id": 1, + "name": "10.145.88.0/23" + } + 404: + body: + application/json: + example: | + { + "message": "Subnetwork with id 'some_id' cannot be found!" + } + description: Gets one subnetwork info + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "subnet": "10.172.20.0/24", + "name": "update_subnet" + } + responses: + 200: + body: + application/json: + example: | + { + "updated_at": "2014-10-18 21:44:17", + "subnet": "10.145.86.0/23", + "created_at": "2014-10-18 21:43:50", + "id": 1, + "name": "10.145.86.0/23" + } + 404: + body: + application/json: + example: | + { + "message": "Subnetwork with id 'some_id' cannot be found!" + } + 409: + body: + application/json: + example: | + { + "message": "Subnet name already exists!" + } + description: set subnet properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 403: + body: + application/json: + example: | + { + "message": "Subnetwork is in use by some interface. Cannot delete it." + } + + + { + "message": "Subnetwork can only be deleted by creator or admin!" + } + 404: + body: + application/json: + example: | + { + "message": "Subnetwork with id 'some_id' cannot be found!" + } + description: Deletes a subnetwork (owner, admin only) + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/clusters: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "created_at": "2014-10-18 23:01:23", + "os_name": "CentOS-6.5-x86_64", + "name": "cluster1", + "reinstall_distributed_system": true, + "adapter_id": 3, + "updated_at": "2014-10-18 23:01:23", + "owner": "admin@huawei.com", + "os_id": 2, + "distributed_system_installed": false, + "flavor": { + "display_name": "All-In-One", + "name": "allinone", + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "role_id": 35, + "flavor_id": 4, + "optional": true, + "id": 35, + "name": "allinone-compute" + } + ], + "adapter_id": 3, + "template": "allinone.tmpl", + "id": 4 + }, + "id": 1 + } + ] + queryParameters: + name: + os_name: + owner: + adapter_name: + flavor_name: + description: Lists all information for all clusters + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + post: + body: + application/json: + schema: | + { + "adapter_id": 3, + "name": "add_cluster", + "os_id": 1, + "flavor_id": 1 + } + responses: + 201: + body: + application/json: + example: | + { + "created_at": "2014-10-18 23:01:23", + "os_name": "CentOS-6.5-x86_64", + "name": "cluster1", + "reinstall_distributed_system": true, + "adapter_id": 3, + "updated_at": "2014-10-18 23:01:23", + "owner": "admin@huawei.com", + "os_id": 2, + "distributed_system_installed": false, + "flavor": { + "display_name": "All-In-One", + "name": "allinone", + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "role_id": 35, + "flavor_id": 4, + "optional": true, + "id": 35, + "name": "allinone-compute" + } + ], + "adapter_id": 3, + "template": "allinone.tmpl", + "id": 4 + }, + "id": 1 + } + 409: + body: + application/json: + example: | + { + "message": "Cluster with name 'cluster_01' already exists!" + } + description: Creates a new cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{cluster_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "created_at": "2014-10-18 23:01:23", + "os_name": "CentOS-6.5-x86_64", + "name": "cluster1", + "reinstall_distributed_system": true, + "adapter_id": 3, + "updated_at": "2014-10-18 23:01:23", + "owner": "admin@huawei.com", + "os_id": 2, + "distributed_system_installed": false, + "flavor": { + "display_name": "All-In-One", + "name": "allinone", + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "role_id": 35, + "flavor_id": 4, + "optional": true, + "id": 35, + "name": "allinone-compute" + } + ], + "adapter_id": 3, + "template": "allinone.tmpl", + "id": 4 + }, + "id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: Lists information for a specified cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "name": "update_cluster" + } + responses: + 200: + body: + application/json: + example: | + { + "created_at": "2014-10-18 23:16:02", + "os_name": "CentOS-6.5-x86_64", + "name": "cluster_new", + "reinstall_distributed_system": true, + "adapter_id": 3, + "updated_at": "2014-10-18 23:16:39", + "owner": "admin@huawei.com", + "os_id": 2, + "distributed_system_installed": false, + "flavor": { + "display_name": "All-In-One", + "name": "allinone", + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "role_id": 35, + "flavor_id": 4, + "optional": true, + "id": 35, + "name": "allinone-compute" + } + ], + "adapter_id": 3, + "template": "allinone.tmpl", + "id": 4 + }, + "id": 2 + } + 400: + body: + application/json: + example: | + { + "message": "Cluster not found" + } + description: set properties of cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "created_at": "2014-10-18 23:01:23", + "os_name": "CentOS-6.5-x86_64", + "name": "cluster1", + "reinstall_distributed_system": true, + "adapter_id": 3, + "updated_at": "2014-10-18 23:01:23", + "owner": "admin@huawei.com", + "os_id": 2, + "distributed_system_installed": false, + "flavor": { + "display_name": "All-In-One", + "name": "allinone", + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "role_id": 35, + "flavor_id": 4, + "optional": true, + "id": 35, + "name": "allinone-compute" + } + ], + "adapter_id": 3, + "template": "allinone.tmpl", + "id": 4 + }, + "id": 1 + } + 403: + body: + application/json: + example: | + { + "message": "Cluster has been deployed or is being installed. Not allowed to delete it now!" + } + description: Deletes a specific cluster before deploy (admin, owner only). Hosts will be still kept even cluster(s) is deleted. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /config: + get: + responses: + 200: + body: + application/json: + example: | + { + "package_config": { + }, + "os_config": { + } + } + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: Gets config information for a specified cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "os_config":{ + "general": { + "language": "EN", + "timezone": "PDT", + "domain": "xxx", + "default_gateway": "10.0.0.1" + }, + "server_credentials": { + "username": "admin", + "password": "admin" + }, + "partition": { + "/var" : { + "_type": "$path", + "max_size": "20", + "size_percentage": "20" + } + } + }, + "package_config":{ + "network_mapping": { + "management": { + "interface": "eth0" + }, + "tenant": { + "interface": "eth1" + }, + "storage": { + "interface":" eth2" + }, + "public": { + "interface": "eth3" + } + } + } + } + responses: + 200: + body: + application/json: + example: | + { + "os_config”: { + "general”: { + "language": "EN", + "timezone": "PDT", + "domain": "xxx", + "default_gateway": "10.0.0.1" + }, + "server_crendentials": { + "username": "admin", + "password": "admin" + }, + "partition": { + "/var" : { + "max_size": "20", + "size_percentage": "20", + }, + } + } + + { + "package_config": { + "network_mapping": { + "management": { + "interface": "eth0" + }, + "tenant": { + "interface": "eth1" + }, + "storage": { + "interface":"eth2" + }, + "public": { + "interface": "eth3" + } + } + } + } + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: set properties in cluster config + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "package_config": { + "security": { + "dashboard_credentials": { + "username": "root" + } + } + } + } + responses: + 200: + body: + application/json: + example: | + { + "package_config":{ + "security": { + "service_crendentials": { + "image": { + "username": "admin", + "password": "admin" + }, + ... + }, + "dashboard_credentials":{ + "username": "root", + "password": "admin" + } + } + } + } + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: update properties in cluster config + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "package_config":{ + "security": { + "service_crendentials": { + "image": { + "username": "admin", + "password": "admin" + }, + ... + } + } + } + } + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: delete cluster config + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /state: + get: + responses: + 200: + body: + application/json: + example: | + { + "package_config": { + }, + "os_config": { + } + } + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: get cluster state + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /hosts: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "id" : 1, + "name": "host_01", + "dns": "xxx", + "os": "Centos", + "mac": "---MAC-address---", + "machine_id": 1, + "os_installed": true, + }, + …... + ] + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: Gets the information of the hosts belonging to this cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + post: + body: + application/json: + schema: | + { + "machine_id": 1, + "name": "cluster_host", + "reinstall_os": "True", + "roles": ["allinone-compute"] + } + responses: + 200: + body: + application/json: + example: | + { + "os_installer": { + "id": 1, + "alias": "cobbler", + "name": "cobbler", + "settings": { + "credentials": { + "username": "cobbler", + "password": "cobbler" + }, + "cobbler_url": "http://10.145.88.211/cobbler_api" + } + }, + "ip": null, + "clusterhost_id": 2, + "updated_at": "2014-10-18 23:47:47", + "switches": [ + { + "switch_ip": "172.29.8.40", + "vlans": [ + 88 + ], + "port": "4" + } + ], + "os_installed": false, + "tag": {}, + "cluster_id": 2, + "id": 2, + "switch_ip": "172.29.8.40", + "networks": { + }, + "hostname": null, + "reinstall_os": true, + "owner": "admin@huawei.com", + "port": "4", + "location": {}, + "os_name": "CentOS-6.5-x86_64", + "reinstall_distributed_system": true, + "mac": "00:0c:29:2b:c9:d4", + "host_id": 2, + "distributed_system_installed": false, + "name": "None.cluster_new", + "roles": [], + "clustername": "cluster_new", + "created_at": "2014-10-18 23:47:47", + "machine_id": 2 + } + 409: + body: + application/json: + example: | + { + "message": "host already exists" + } + description: add host to a cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{host_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "id" : 1, + "name": "host_01", + "dns": "xxx", + "os": "Centos", + "mac": "---MAC-address---", + "machine_id": 1, + "os_installed": true, + "links": [ + { + "href" : "/hosts/1", + "rel": "self" + }, + { + "href": "/clusters/1/hosts/1/config", + "rel": "host package config" + } + ] + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: get host of a cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "name": "update_cluster_host", + "reinstall_os": "False", + "roles": ["ha-proxy"] + } + responses: + 200: + body: + application/json: + example: | + { + "os_installer": { + "id": 1, + "alias": "cobbler", + "name": "cobbler", + "settings": { + "credentials": { + "username": "cobbler", + "password": "cobbler" + }, + "cobbler_url": "http://10.145.88.211/cobbler_api" + } + }, + "ip": null, + "clusterhost_id": 2, + "updated_at": "2014-10-19 00:10:43", + "switches": [ + { + "switch_ip": "172.29.8.40", + "vlans": [ + 88 + ], + "port": "4" + } + ], + "os_installed": false, + "tag": {}, + "cluster_id": 2, + "id": 2, + "switch_ip": "172.29.8.40", + "networks": {}, + "hostname": null, + "reinstall_os": true, + "owner": "admin@huawei.com", + "port": "4", + "location": {}, + "os_name": "CentOS-6.5-x86_64", + "reinstall_distributed_system": true, + "mac": "00:0c:29:2b:c9:d4", + "host_id": 2, + "distributed_system_installed": false, + "name": "None.cluster_new", + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "optional": true, + "id": 35, + "name": "allinone-compute" + } + ], + "clustername": "cluster_new", + "created_at": "2014-10-18 23:47:47", + "machine_id": 2 + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: set host properties of a cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "roles": "os-controller" + } + responses: + 200: + body: + application/json: + example: | + { + "os_installer": { + "id": 1, + "alias": "cobbler", + "name": "cobbler", + "settings": { + "credentials": { + "username": "cobbler", + "password": "cobbler" + }, + "cobbler_url": "http://10.145.88.211/cobbler_api" + } + }, + "ip": null, + "clusterhost_id": 2, + "updated_at": "2014-10-19 00:10:43", + "switches": [ + { + "switch_ip": "172.29.8.40", + "vlans": [ + 88 + ], + "port": "4" + } + ], + "os_installed": false, + "tag": {}, + "cluster_id": 2, + "id": 2, + "switch_ip": "172.29.8.40", + "networks": {}, + "hostname": null, + "reinstall_os": true, + "owner": "admin@huawei.com", + "port": "4", + "location": {}, + "os_name": "CentOS-6.5-x86_64", + "reinstall_distributed_system": true, + "mac": "00:0c:29:2b:c9:d4", + "host_id": 2, + "distributed_system_installed": false, + "name": "None.cluster_new", + "roles": [ + { + "display_name": "all in one compute", + "description": "all in one compute", + "adapter_id": 3, + "optional": true, + "id": 35, + "name": "allinone-compute" + }, + { + "name": "new-role", + ... + } + ], + "clustername": "cluster_new", + "created_at": "2014-10-18 23:47:47", + "machine_id": 2 + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: update host properties of a cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + description: delete host from a cluster + /config: + get: + responses: + 200: + body: + application/json: + example: | + { + "os_config": { + ... + }, + "package_config": { + ... + } + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: get config of a host + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "os_config": { + "general": { + "language": "EN", + "timezone": "UTC", + "http_proxy": "http://127.0.0.1:3128", + "https_proxy": "http://127.0.0.1:3128", + "no_proxy": [ + "127.0.0.1", + "compass" + ], + "ntp_server": "127.0.0.1", + "dns_servers": [ + "127.0.0.1" + ], + "domain": "ods.com", + "search_path": [ + "ods.com" + ], + "default_gateway": "127.0.0.1" + }, + "server_credentials": { + "username": "root", + "password": "root" + }, + "partition": { + "/var": { + "max_size": "100G", + "percentage": 10, + "size": "1G" + } + } + }, + "package_config": { + "network_mapping": { + "management": { + "interface": "eth0" + }, + "tenant": { + "interface": "eth1" + }, + "storage": { + "interface":"eth2" + }, + "public": { + "interface": "eth3" + } + }, + "services_credentials": { + "image": { + "username": "xxx", + "password": "xxx" + }, + "metering": { + "username": "xxx", + "password": "xxx" + } + } + } + } + responses: + 200: + body: + application/json: + example: | + { + ….. + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: set host config + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "os_config": { + "general": { + "language": "EN", + "timezone": "UTC", + "http_proxy": "http://127.0.0.1:3128", + "https_proxy": "http://127.0.0.1:3128", + "no_proxy": [ + "127.0.0.1", + "compass" + ], + "ntp_server": "127.0.0.1", + "dns_servers": [ + "127.0.0.1" + ], + "domain": "ods.com", + "search_path": [ + "ods.com" + ], + "default_gateway": "127.0.0.1" + }, + "server_credentials": { + "username": "root", + "password": "root" + }, + "partition": { + "/var": { + "max_size": "100G", + "percentage": 10, + "size": "1G" + } + } + }, + "package_config": { + "network_mapping": { + "management": { + "interface": "eth0" + }, + "tenant": { + "interface": "eth1" + }, + "storage": { + "interface":"eth2" + }, + "public": { + "interface": "eth3" + } + }, + "services_credentials": { + "image": { + "username": "xxx", + "password": "xxx" + }, + "metering": { + "username": "xxx", + "password": "xxx" + } + } + } + } + responses: + 200: + body: + application/json: + example: | + { + "os_config": { + ...//the same as PATCH cluster config + } + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: update host config + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "os_config": { + ...//the same as PATCH cluster config + } + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: delete host config + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /state: + get: + responses: + 200: + body: + application/json: + example: | + { + "cluster_id" : 1, + "host_id": 10 + "state": "INSTALLING", + "percentage": 0.5, + "severity": "INFO", + "message": "-----some--message-----", + "updated_at": "---timestamp---" + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: get host state of a cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "state": "INSTALLING" + } + responses: + 200: + body: + application/json: + example: | + { + "cluster_id" : 1, + "host_id": 10 + "state": "SUCCESSFUL", + "percentage": 1, + "severity": "INFO", + "message": "-----some--message-----", + "updated_at": "---timestamp---" + } + OR + { + "cluster_id" : 1, + "host_id": 10 + "state": "ERROR", + "percentage": 0.7, + "severity": "ERROR", + "message": "---some-error-message---", + "updated_at": "---timestamp---" + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: set host state properties of a cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /action: + post: + body: + application/json: + schema: | + { + "add_hosts": { + "machines": [{ + "machine_id": 1, + "host_id": 1, + "reinstall_os": "True" + },{ + "machine_id": 2, + "host_id": 2 + }] + }, + "set_hosts": { + "machines": [{ + "machine_id": 3 + },{ + "machine_id": 4 + }] + }, + "remove_hosts": { + "hosts": [1] + }, + "review": { + "hosts": [1,2,3] + }, + "deploy": { + "hosts": [1,2,3] + } + } + responses: + 200: + body: + application/json: + example: | + { + "hosts": [ + { + "id" : 5, + "machine_id": 10 + }, + { + "id" : 6, + "machine_id": 11 + }, + { + "id" : 7, + "machine_id": 12 + } + ] + } + + OR + + { + "hosts": [ + { + "id" : 1, + "machine_id": 13 + }, + { + "id" : 2, + "machine_id": 14 + }, + { + "id" : 3, + "machine_id": 15 + } + ] + } + + OR + + { + "hosts": [ + { + "id" : 1, + "machine_id": 13 + } + ] + } + + OR + { + "hosts": [ + { + "id" : 1, + "machine_id": 10 + }, + { + "id" : 2, + "machine_id": 11 + }, + { + "id" : 3, + "machine_id": 12 + } + ] + } + + OR + + { + "cluster": {"id": 1}, + "hosts": [{"id": 1}, {"id": 2}, {"id": 3}] + } + + OR + + { + "status": "deploy action sent", + "cluster": { + "id": 1, + }, + "hosts": [ + { + "id": 3 + } + ] + } + + + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: Takes an action for a specific cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /metadata: + get: + responses: + 200: + body: + application/json: + example: | + { + "package_config": { + }, + "os_config": { + } + } + 404: + body: + application/json: + example: | + { + "message": "Cluster with id 'some_id' cannot be found!" + } + description: Get metadata of a specific cluster + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + +/hosts: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "id” : 1, + "name": "host_01", + "machine_id": 1, + "mac": "---MAC-address--", + "ip": "192.168.1.2", + "os": "CentOS", + "os_installed": false, + "clusters": ["cluster_01"], + "created_by": "user1@email.com", + "created_at": "---timestamp---", + "updated_at": "---timestamp---", + "links”: [ + { + "href" : "/hosts/1", + "rel": "self + } + ] + }, + ... + ] + queryParameters: + name: + os_name: + owner: + mac: + description: Lists information for all hosts + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{host_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "id" : 1, + "name": "host_01", + "machine_id": 1, + "mac": "---MAC-address--”, + "ip": "192.168.1.2" + "os": "CentOs", + "os_installed": false, + "domain": "xxx", + "dns": "xxx", + "created_by": "user1@email.com", + "created_at": "---timestamp---", + "updated_at": "---timestamp---" + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: Lists information for a specified host + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "name": "update_host_name" + } + responses: + 200: + body: + application/json: + example: | + { + "id" : 1, + "name": "host1" + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: set host properties. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "id" : 1, + "name": "host_01_new", + "mac": "---MAC-address--", + "os_name": "CentOs", + "os_installed": false + } + 404: + body: + application/json: + example: | + { + "type": "itemNotFound", + "message": " Host with id 'some_id' cannot be found!" + } + description: Deletes a host (admin only). The host must be not in any cluster. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /action: + post: + body: + application/json: + schema: | + { + "poweron": [1], + "poweroff": [1], + "reset": [1] + } + responses: + 200: + body: + application/json: + example: | + { + "status": "host power action sent", + "host": {...} + } + 404: + body: + application/json: + example: | + { + "message": "The host witch ID '$host_id' cannot be found!" + } + 400: + body: + application/json: + example: | + { + "message": "The host didnot set IPMI info!" + } + description: Poweron, poweroff, reset this host by IPMI + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /clusters: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "os_name": "CentOS-6.5-x86_64", + "name": "cluster_new", + "reinstall_distributed_system": true, + "created_at": "2014-10-18 23:16:02", + "adapter_id": 3, + "updated_at": "2014-10-18 23:16:39", + "owner": "admin@huawei.com", + "distributed_system_installed": false, + "id": 2 + } + ] + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: Lists clusters which the host belongs to + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /config: + get: + responses: + 200: + body: + application/json: + example: | + { + "os_config": { + "global": { + "language": "EN", + "timezone": "PDT", + } + "partition": { + "/var": { + "max_size": "20", + "size_percentage": "30" + }, + "/home": { + "max_size": "20", + "size_percentage": "40" + } + } + } + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: Lists config information for a specified host + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "os_config": { + "general": { + "language": "EN", + "timezone": "UTC", + "http_proxy": "http://127.0.0.1:3128", + "https_proxy": "http://127.0.0.1:3128", + "no_proxy": [ + "127.0.0.1", + "compass" + ], + "ntp_server": "127.0.0.1", + "dns_servers": [ + "127.0.0.1" + ], + "domain": "ods.com", + "search_path": [ + "ods.com" + ], + "default_gateway": "127.0.0.1" + }, + "server_credentials": { + "username": "root", + "password": "root" + }, + "partition": { + "/var": { + "max_size": "100G", + "percentage": 10, + "size": "1G" + } + } + } + } + responses: + 200: + body: + application/json: + example: | + { + "os_config": { + … + } + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: set config properties for a specified host + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "os_config": { + "general": { + "language": "EN", + "timezone": "UTC", + "http_proxy": "http://127.0.0.1:3128", + "https_proxy": "http://127.0.0.1:3128", + "no_proxy": [ + "127.0.0.1", + "compass" + ], + "ntp_server": "127.0.0.1", + "dns_servers": [ + "127.0.0.1" + ], + "domain": "ods.com", + "search_path": [ + "ods.com" + ], + "default_gateway": "127.0.0.1" + }, + "server_credentials": { + "username": "root", + "password": "root" + }, + "partition": { + "/var": { + "max_size": "100G", + "percentage": 10, + "size": "1G" + } + } + } + } + responses: + 200: + body: + application/json: + example: | + { + .... + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: update host config properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "os_config": { + ... + } + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: delete host config + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /state: + get: + responses: + 200: + body: + application/json: + example: | + { + "state": "INSTALLING", + "percentage": 0.5, + "severity": "INFO", + "message": "-----some--message-----", + "updated_at": "---timestamp---" + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: get host state + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "state": "INSTALLING" + } + responses: + 200: + body: + application/json: + example: | + { + "cluster_id" : 1, + "host_id": 10 + "state": "SUCCESSFUL", + "percentage": 1, + "severity": "INFO", + "message": "-----some--message-----", + "updated_at": "---timestamp---" + } + + OR + + { + "cluster_id" : 1, + "host_id": 10 + "state": "ERROR", + "percentage": 0.7, + "severity": "ERROR", + "message": "---some-error-message---", + "updated_at": "---timestamp---" + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: set host state properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /network: + get: + body: + application/json: + schema: | + [ + { + "interface": "eth0", + "ip": "10.172.20.91", + "subnet_id": 1, + "is_mgmt": "False", + "is_promiscuous": "False" + }, + { + "interface": "eth1", + "ip": "10.172.20.110", + "subnet_id": 1, + "is_mgmt": "False", + "is_promiscuous": "False" + } + ] + responses: + 200: + body: + application/json: + example: | + { + "eth0": { + "id": 1, + "interface": "eth0", + "ip": "192.168.10.1", + "is_mgmt": true, + "is_promiscuous": false, + "subnet_id": 1, + }, + "eth1": { + "id": 2, + "interface": "eth1", + "ip": "10.12.123.1", + "is_promiscuous": true, + "subnet_id": 2, + }, + ….. + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: Lists network info for a specified host + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + post: + body: + application/json: + schema: | + { + "interface": "eth0", + "ip": "10.145.89.152", + "subnet_id": 1, + "is_mgmt": "True", + "is_promiscuous": "False" + } + responses: + 200: + body: + application/json: + example: | + { + "id": 3, + "interface": "eth3", + "ip": "12.140.10.1", + "is_promiscuous": true, + "is_mgmt": false, + "subnet_id": 3, + } + 404: + body: + application/json: + example: | + { + "message": " Host with id ‘some_id’ cannot be found!" + } + description: Creates an interface config entry + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{interface}: + get: + description: list host network information + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "interface": "eth1", + "ip": "10.145.89.155", + "subnet_id": 1, + "is_mgmt": "True", + "is_promiscuous": "False" + } + responses: + 200: + body: + application/json: + example: | + { + "id": 3, + "interface": "eth3", + "ip": "12.140.10.2", + "is_promiscuous": true, + "is_mgmt": false, + "subnet_id": 4, + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: set host network properties + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "id": 3, + "interface": "eth3", + "ip": "12.140.10.1", + "is_promiscuous”: true, + "is_mgmt": false, + "subnet_id": 3 + } + 404: + body: + application/json: + example: | + { + "message": " Host with id 'some_id' cannot be found!" + } + description: delete a host network + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/proxy/{path}: + get: + responses: + 200: + body: + application/json: + example: | + [ + { + "created_at": "2014-10-19 10:50:04", + "updated_at": "2014-10-19 10:50:04", + "email": "admin@huawei.com", + "is_admin": true, + "active": true, + "id": 1 + } + ] + queryParameters: + URL: + example: http://10.145.88.211/api/proxy/users + description: proxy get request + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + post: + body: + application/json: + schema: | + { + "url": "http://10.145.88.211/api/proxy/subnets" + } + responses: + 200: + body: + application/json: + example: | + { + "subnet": "10.145.86.0/23", + "created_at": "2014-10-19 11:25:33", + "updated_at": "2014-10-19 11:25:33", + "name": "10.145.86.0/23", + "id": 3 + } + description: proxy post request + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + put: + body: + application/json: + schema: | + { + "url": "http://10.145.88.211/api/proxy/subnets/3" + } + responses: + 200: + body: + application/json: + example: | + { + "subnet": "10.145.84.0/23", + "created_at": "2014-10-19 11:25:33", + "updated_at": "2014-10-19 11:29:08", + "name": "10.145.84.0/23", + "id": 3 + } + description: proxy put request + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + patch: + body: + application/json: + schema: | + { + "url": "http://10.145.88.211/api/proxy/subnets/3" + } + responses: + 200: + body: + application/json: + example: | + { + "ip": "172.29.8.42", + "created_at": "2014-10-19 11:31:40", + "updated_at": "2014-10-19 11:33:46", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "private" + }, + "id": 3 + } + description: proxy patch request + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "ip": "172.29.8.42", + "created_at": "2014-10-19 11:31:40", + "updated_at": "2014-10-19 11:33:46", + "state": "initialized", + "filters": "", + "credentials": { + "version": "2c", + "community": "private" + }, + "id": 3 + } + queryParameters: + URL: + example: http://10.145.88.211/api/proxy/switches/3 + description: proxy delete request + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/host/networks: + get: + responses: + 200: + body: + application/json: + example: | + { + "eth1": { + "ip": "192.168.100.155", + "created_at": "2015-04-17 14:55:55", + "is_promiscuous": true, + "updated_at": "2015-04-17 14:55:55", + "netmask": "255.255.254.0", + "is_mgmt": false, + "interface": "eth1", + "id": 1 + }, + "eth0": { + "ip": "10.145.89.155", + "created_at": "2015-04-17 14:55:55", + "is_promiscuous": false, + "updated_at": "2015-04-17 14:55:55", + "netmask": "255.255.254.0", + "is_mgmt": true, + "interface": "eth0", + "id": 2 + } + } + description: List all host networks + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + /{host_network_id}: + get: + responses: + 200: + body: + application/json: + example: | + { + "ip": "192.168.100.155", + "created_at": "2015-04-17 14:55:55", + "is_promiscuous": true, + "updated_at: "2015-04-17 14:55:55", + "netmask": "255.255.254.0", + "is_mgmt": false, + "interface": "eth1", + "id": 1 + } + 404: + body: + application/json: + example: | + { + "message": "Cannot find the record in table HostNetwork: {'id': }", + } + description: List specifig host network info + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 +/host-networks/{host_network_id}: + put: + body: + application/json: + schema: | + { + "interface": "eth0", + "ip": "10.145.88.10" + } + responses: + 200: + body: + application/json: + example: | + { + "ip": "192.168.100.159", + "created_at": "2015-04-17 14:55:55", + "is_promiscuous": true, + "updated_at: "2015-04-17 14:55:55", + "netmask": "255.255.254.0", + "is_mgmt": false, + "interface": "eth1", + "id": 1 + } + 404: + body: + application/json: + example: | + { + message: "Cannot find the record in table HostNetwork: {'id': }" + } + description: Update a specific host network info. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + delete: + responses: + 200: + body: + application/json: + example: | + { + "ip: "10.145.89.155", + "created_at": "2015-04-17 15:44:54" + "is_promiscuous": false, + "updated_at": "2015-04-17 15:44:54", + "netmask": "255.255.254.0", + "is_mgmt": false + "interface": "eth0", + "id": 1 + } + 404: + body: + application/json: + example: | + { + message: "Cannot find the record in table HostNetwork: {'id': }" + } + description: Delete a host network. + headers: + Access-token: + displayName: X-Auth-Header + required: true + example: $1$fCD2zLIa$hikkNkqDe0qAXgKHDzw0E0 + + + diff --git a/compass-deck/api/auth_handler.py b/compass-deck/api/auth_handler.py new file mode 100644 index 0000000..3c22ebb --- /dev/null +++ b/compass-deck/api/auth_handler.py @@ -0,0 +1,49 @@ +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itsdangerous import BadData +import logging +import sys + +from compass.api import app +from compass.api import exception_handler +from compass.api import login_manager + +from compass.db.api import user as user_api +from compass.db.api.user import UserWrapper + + +def authenticate_user(email, password, **kwargs): + """Authenticate a user by email and password.""" + user = user_api.get_user_object( + email, **kwargs + ) + user.authenticate(password) + return user + + +@login_manager.token_loader +def load_user_from_token(token): + return user_api.get_user_object_from_token(token) + + +@login_manager.header_loader +def load_user_from_header(header): + """Return a user object from token.""" + return user_api.get_user_object_from_token(header) + + +@login_manager.user_loader +def load_user(token): + return user_api.get_user_object_from_token(token) diff --git a/compass-deck/api/exception_handler.py b/compass-deck/api/exception_handler.py new file mode 100644 index 0000000..67c780e --- /dev/null +++ b/compass-deck/api/exception_handler.py @@ -0,0 +1,92 @@ +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Exceptions for RESTful API.""" +import logging +import simplejson as json +import traceback + +from compass.api import app +from compass.api import utils + + +class HTTPException(Exception): + def __init__(self, message, status_code): + super(HTTPException, self).__init__(message) + self.traceback = traceback.format_exc() + self.status_code = status_code + + def to_dict(self): + return {'message': str(self)} + + +class ItemNotFound(HTTPException): + """Define the exception for referring non-existing object.""" + def __init__(self, message): + super(ItemNotFound, self).__init__(message, 410) + + +class BadRequest(HTTPException): + """Define the exception for invalid/missing parameters. + + User making a request in invalid state cannot be processed. + """ + def __init__(self, message): + super(BadRequest, self).__init__(message, 400) + + +class Unauthorized(HTTPException): + """Define the exception for invalid user login.""" + def __init__(self, message): + super(Unauthorized, self).__init__(message, 401) + + +class UserDisabled(HTTPException): + """Define the exception for disabled users.""" + def __init__(self, message): + super(UserDisabled, self).__init__(message, 403) + + +class Forbidden(HTTPException): + """Define the exception for invalid permissions.""" + def __init__(self, message): + super(Forbidden, self).__init__(message, 403) + + +class BadMethod(HTTPException): + """Define the exception for invoking unsupported methods.""" + def __init__(self, message): + super(BadMethod, self).__init__(message, 405) + + +class ConflictObject(HTTPException): + """Define the exception for creating an existing object.""" + def __init__(self, message): + super(ConflictObject, self).__init__(message, 409) + + +@app.errorhandler(Exception) +def handle_exception(error): + if hasattr(error, 'to_dict'): + response = error.to_dict() + else: + response = {'message': str(error)} + if app.debug and hasattr(error, 'traceback'): + response['traceback'] = error.traceback + + status_code = 400 + if hasattr(error, 'status_code'): + status_code = error.status_code + + return utils.make_json_response(status_code, response) diff --git a/compass-deck/api/utils.py b/compass-deck/api/utils.py new file mode 100644 index 0000000..87977cd --- /dev/null +++ b/compass-deck/api/utils.py @@ -0,0 +1,35 @@ +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utils for API usage.""" +from flask import make_response +import simplejson as json + + +def make_json_response(status_code, data): + """Wrap json format to the reponse object.""" + + result = json.dumps(data, indent=4) + '\r\n' + resp = make_response(result, status_code) + resp.headers['Content-type'] = 'application/json' + return resp + + +def make_csv_response(status_code, csv_data, fname): + """Wrap CSV format to the reponse object.""" + fname = '.'.join((fname, 'csv')) + resp = make_response(csv_data, status_code) + resp.mimetype = 'text/csv' + resp.headers['Content-Disposition'] = 'attachment; filename="%s"' % fname + return resp diff --git a/compass-deck/api/v1/__init__.py b/compass-deck/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/compass-deck/api/v1/api.py b/compass-deck/api/v1/api.py new file mode 100644 index 0000000..9dbc548 --- /dev/null +++ b/compass-deck/api/v1/api.py @@ -0,0 +1,248 @@ +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Define all the RestfulAPI entry points.""" +import logging +import simplejson as json + +from flask import Blueprint +from flask import request + +from flask.ext.restful import Resource + +from compass.api.exception import BadRequest +from compass.api.exception import Forbidden +from compass.api.exception import ItemNotFound +from compass.api.exception import Unauthorized +from compass.api.restfulAPI import CompassApi +from compass.api import utils + +from compass.db import db_api +from compass.db.exception import InvalidParameter +from compass.db.exception import RecordNotExists + + +v1_app = Blueprint('v1_app', __name__) +api = CompassApi(v1_app) +PREFIX = '/v1.0' + + +@v1_app.route('/users', methods=['GET']) +def list_users(): + """List details of all users filtered by user email and admin role.""" + + emails = request.args.getlist('email') + is_admin = request.args.get('admin') + filters = {} + + if emails: + filters['email'] = emails + + if is_admin is not None: + if is_admin == 'true': + filters['is_admin'] = True + elif is_admin == 'false': + filters['is_admin'] = False + + users_list = db_api.user.list_users(filters) + + return utils.make_json_response(200, users_list) + + +class User(Resource): + ENDPOINT = PREFIX + '/users' + + def get(self, user_id): + """Get user's information for the specified ID.""" + try: + user_data = db_api.user.get_user(user_id) + logging.debug("user_data is===>%s", user_data) + + except RecordNotExists as ex: + error_msg = ex.message + raise ItemNotFound(error_msg) + + return utils.make_json_response(200, user_data) + + +class Adapter(Resource): + ENDPOINT = PREFIX + "/adapters" + + def get(self, adapter_id): + """Get information for a specified adapter.""" + + try: + adapter_info = db_api.adapter.get_adapter(adapter_id) + except RecordNotExists as ex: + error_msg = ex.message + raise ItemNotFound(error_msg) + + return utils.make_json_response(200, adapter_info) + + +@v1_app.route('/adapters', methods=['GET']) +def list_adapters(): + """List details of all adapters filtered by the adapter name(s).""" + + names = request.args.getlist('name') + filters = {} + if names: + filters['name'] = names + + adapters_list = db_api.adapter.list_adapters(filters) + return utils.make_json_response(200, adapters_list) + + +@v1_app.route('/adapters//config-schema', methods=['GET']) +def get_adapter_config_schema(adapter_id): + """Get the config schema for a specified adapter.""" + + os_id = request.args.get("os-id", type=int) + + try: + schema = db_api.adapter.get_adapter_config_schema(adapter_id, os_id) + except RecordNotExists as ex: + raise ItemNotFound(ex.message) + + return utils.make_json_response(200, schema) + + +@v1_app.route('/adapters//roles', methods=['GET']) +def get_adapter_roles(adapter_id): + """Get roles for a specified adapter.""" + + try: + roles = db_api.adapter.get_adapter(adapter_id, True) + except RecordNotExists as ex: + raise ItemNotFound(ex.message) + + return utils.make_json_response(200, roles) + + +class Cluster(Resource): + def get(self, cluster_id): + """Get information for a specified cluster.""" + + try: + cluster_info = db_api.cluster.get_cluster(cluster_id) + + except RecordNotExists as ex: + error_msg = ex.message + raise ItemNotFound(error_msg) + + return utils.make_json_response(200, cluster_info) + + +@v1_app.route('/clusters//config', methods=['PUT', 'PATCH']) +def add_cluster_config(cluster_id): + """Update the config information for a specified cluster.""" + config = json.loads(request.data) + if not config: + raise BadRequest("Config cannot be None!") + + root_elems = ['os_config', 'package_config'] + if len(config.keys()) != 1 or config.keys()[0] not in root_elems: + error_msg = ("Config root elements must be either" + "'os_config' or 'package_config'") + raise BadRequest(error_msg) + + result = None + is_patch_method = request.method == 'PATCH' + try: + if "os_config" in config: + result = db_api.cluster\ + .update_cluster_config(cluster_id, + 'os_config', + config, + patch=is_patch_method) + elif "package_config" in config: + result = db_api.cluster\ + .update_cluster_config(cluster_id, + 'package_config', config, + patch=is_patch_method) + + except InvalidParameter as ex: + raise BadRequest(ex.message) + + except RecordNotExists as ex: + raise ItemNotFound(ex.message) + + return utils.make_json_response(200, result) + + +api.add_resource(User, + '/users', + '/users/') +api.add_resource(Adapter, + '/adapters', + '/adapters/') +api.add_resource(Cluster, + '/clusters', + '/clusters/') + + +@v1_app.errorhandler(ItemNotFound) +def handle_not_exist(error, failed_objs=None): + """Handler of ItemNotFound Exception.""" + + message = {'type': 'itemNotFound', + 'message': error.message} + + if failed_objs and isinstance(failed_objs, dict): + message.update(failed_objs) + + return utils.make_json_response(404, message) + + +@v1_app.errorhandler(Unauthorized) +def handle_invalid_user(error, failed_objs=None): + """Handler of Unauthorized Exception.""" + + message = {'type': 'unathorized', + 'message': error.message} + + if failed_objs and isinstance(failed_objs, dict): + message.update(failed_objs) + + return utils.make_json_response(401, message) + + +@v1_app.errorhandler(Forbidden) +def handle_no_permission(error, failed_objs=None): + """Handler of Forbidden Exception.""" + + message = {'type': 'Forbidden', + 'message': error.message} + + if failed_objs and isinstance(failed_objs, dict): + message.update(failed_objs) + + return utils.make_json_response(403, message) + + +@v1_app.errorhandler(BadRequest) +def handle_bad_request(error, failed_objs=None): + """Handler of badRequest Exception.""" + + message = {'type': 'badRequest', + 'message': error.message} + + if failed_objs and isinstance(failed_objs, dict): + message.update(failed_objs) + + return utils.make_json_response(400, message) + + +if __name__ == '__main__': + v1_app.run(debug=True) -- cgit 1.2.3-korg