murano-repository/muranorepository/api/v1.py
Ekaterina Fedorova d353dacf8e Cherry-pick the following commits from release-0.4
* Add forgotten return statements
	Closes-bug: #1268934

* Fix error code when there is no input json

* Return correct http code

	During toggle enabled 500 was sent in case service is not defined
	Fix return code to 404
	Closes-Bug: #1268976

* Remove need to specify IP for load balancer
	Implements:
	   https://blueprints.launchpad.net/murano/+spec/auto-assign-virtual-ip
	Address blueprint auto-assign-virtual-ip
	Fix errors in infrastructure
	1) Update path to config file
	2) Update sample config - remove non-existing directory
	3) Add 0.4.1 version
	Fixes-Bug: 1270734

* Add new setup and SysV scripts

* Removed SysV EL6 standalone file, removed old setup scripts

* Add correct error message when no config specified
	Closes-Bug: 1271092

* Security rules updated
	* incorrect port ranges for ADDS fixed according to
	http://technet.microsoft.com/en-us/library/dd772723%28v=WS.10%29.aspx
	* security template for Windows Server Failover Cluster added according to
	http://support.microsoft.com/kb/832017#method5
	* security rules for SQL Server updated according to
	http://technet.microsoft.com/en-us/library/cc646023.aspx

	Relates-Bug: 1264088

* Typo fixed

* Revert change
	This reverts commit d87bc2309f.

* Path flattening is reverted, but opening ports for WinRM 2.0 is kept.
	Related-Bug: #1271578

* Fix paths to scripts used by MS SQL Cluster templates.
	Partial-Bug: #1271578

* Fix returning list of files in nested dirs - don't cut first symbol.

* And fix a minor PyCharm warning about var not being initialized.
	Closes-Bug: #1274851

* Add checkbox to enable floating IP auto assignment

* Implements blueprint auto-assign-floating-ip

* Fixed typo in conductor workflow
	Closes-Bug: 1264250

* Add service version during service creation
	Closes-Bug: 1269360

* Resolve issue with KeyPair assignment
	nvironment with a service with Key Pair assigned
	could not be deployed due to invalid match in workflows
	causing invalid Heat template to be produced by Conductor.
	Closes-bug: #1274011

* Correct inform message during floating ip creation

* Fix name for syslog_log_facility param

Change-Id: Id3ad4581cd9ce40a569ac580d0aee8db017855c4
2014-02-11 12:40:50 +00:00

260 lines
9.4 KiB
Python

# Copyright (c) 2013 Mirantis, Inc.
#
# 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 os
import tarfile
import tempfile
import json
from flask import Blueprint, send_file
from flask import jsonify, request, abort
from flask import make_response
from muranorepository.api import utils as api_utils
from muranorepository.utils.parser import ManifestParser
from muranorepository.utils.archiver import Archiver
from muranorepository.consts import DATA_TYPES, MANIFEST
from muranorepository.consts import CLIENTS_DICT
from muranorepository.openstack.common.gettextutils import _ # noqa
from oslo.config import cfg
import logging as log
v1_api = Blueprint('v1', __name__)
CONF = cfg.CONF
@v1_api.route('/client/<path:client_type>')
def get_archive_data(client_type):
if client_type not in CLIENTS_DICT.keys():
abort(404)
path_to_archive = api_utils.get_archive(client_type,
request.args.get('hash'))
if path_to_archive:
return send_file(path_to_archive, mimetype='application/octet-stream')
else:
return make_response('Not modified', 304)
@v1_api.route('/client/services/<service_name>')
def download_service_archive(service_name):
# In the future service name may contains dots
api_utils.check_service_name(service_name)
manifests = ManifestParser().parse()
services_for_download = [manifest for manifest in manifests
if manifest.full_service_name == service_name]
if not services_for_download:
abort(404)
if len(services_for_download) != 1:
return make_response(_('Fully qualified service name is not unique'),
500)
archive_manager = Archiver(dst_by_data_type=True)
#ToDo: Create new class to prevent opening twice the same file for writing
with tempfile.NamedTemporaryFile() as tempf:
try:
file = archive_manager.create_service_archive(
services_for_download[0], tempf.name)
except:
log.exception(_('Unable to create service archive'))
abort(500)
else:
return send_file(file, mimetype='application/octet-stream')
@v1_api.route('/admin/<data_type>')
def get_data_type_locations(data_type):
api_utils.check_data_type(data_type)
result_path = api_utils.compose_path(data_type)
return api_utils.get_locations(data_type, result_path)
@v1_api.route('/admin/<data_type>', methods=['POST'])
def upload_file(data_type):
api_utils.check_data_type(data_type)
filename = request.args.get('filename')
return api_utils.save_file(request, data_type,
path=None, filename=filename)
@v1_api.route('/admin/<data_type>/<path:path>')
def get_locations_in_nested_path_or_get_file(data_type, path):
api_utils.check_data_type(data_type)
result_path = api_utils.compose_path(data_type, path)
if os.path.isfile(result_path):
return send_file(result_path, mimetype='application/octet-stream')
else:
return api_utils.get_locations(data_type, result_path)
@v1_api.route('/admin/<data_type>/<path:path>', methods=['POST'])
def upload_file_in_nested_path(data_type, path):
api_utils.check_data_type(data_type)
if data_type == MANIFEST:
return make_response(_('It is forbidden to upload '
'manifests to subfolders'), 403)
return api_utils.save_file(request, data_type, path)
@v1_api.route('/admin/<data_type>/<path:path>', methods=['PUT'])
def create_dirs(data_type, path):
api_utils.check_data_type(data_type)
result_path = api_utils.compose_path(data_type, path)
resp = jsonify(result='success')
if os.path.exists(result_path):
return resp
if data_type == MANIFEST:
return make_response(_('It is forbidden to create '
'directories for manifest files'), 403)
try:
os.makedirs(result_path)
except OSError:
log.exception(_("Error during creating folders"))
abort(403)
return resp
@v1_api.route('/admin/<data_type>/<path:path>', methods=['DELETE'])
def delete_directory_or_file(data_type, path):
api_utils.check_data_type(data_type)
result_path = api_utils.compose_path(data_type, path)
if not os.path.exists(result_path):
log.info(_("Attempt to delete '{0}' failed:"
"specified path doesn't exist"))
abort(404)
if os.path.isfile(result_path):
try:
os.remove(result_path)
except OSError:
log.exception(_("Something went wrong during deletion"
" '{0}' file".format(result_path)))
abort(500)
else:
try:
# enable to delete only empty directories
os.rmdir(result_path)
except OSError:
return make_response(_('Directory must be empty to be deleted'),
403)
api_utils.reset_cache()
return jsonify(result='success')
@v1_api.route('/admin/services')
def get_services_list():
# Do we need to check whether manifest is valid here
manifests = ManifestParser().parse()
excluded_fields = set(DATA_TYPES) - set(MANIFEST)
data = []
for manifest in manifests:
data.append(dict((k, v) for k, v in manifest.__dict__.iteritems()
if not k in excluded_fields))
return jsonify(services=data)
@v1_api.route('/admin/services/<service_name>')
def get_files_for_service(service_name):
api_utils.check_service_name(service_name)
manifest = ManifestParser().parse_manifest(service_name)
if not manifest:
abort(404)
data = api_utils.get_manifest_files(manifest)
return jsonify(data)
@v1_api.route('/admin/services/<service_name>/info')
def get_service_info(service_name):
api_utils.check_service_name(service_name)
manifest = ManifestParser().parse_manifest(service_name)
if not manifest:
abort(404)
data = api_utils.get_manifest_info(manifest)
return jsonify(data)
@v1_api.route('/admin/services', methods=['POST'])
def upload_new_service():
path_to_archive = api_utils.save_archive(request)
if not tarfile.is_tarfile(path_to_archive):
return make_response(_('Uploading file should be a tar.gz archive'),
400)
archive_manager = Archiver()
result = archive_manager.extract(path_to_archive)
if result:
api_utils.reset_cache()
return jsonify(result='success')
else:
return make_response(_('Uploading file failed.'), 400)
@v1_api.route('/admin/services/<service_name>', methods=['DELETE'])
def delete_service(service_name):
api_utils.check_service_name(service_name)
manifests = ManifestParser().parse()
manifest_for_deletion = None
# Search for manifest to delete
for manifest in manifests:
if manifest.full_service_name == service_name:
manifest_for_deletion = manifest
files_for_deletion = api_utils.get_manifest_files(
manifest_for_deletion)
manifests.remove(manifest_for_deletion)
break
if not manifest_for_deletion:
abort(404)
files_for_deletion = api_utils.exclude_common_files(files_for_deletion,
manifests)
return api_utils.perform_deletion(files_for_deletion,
manifest_for_deletion)
@v1_api.route('/admin/services/<service_name>/toggle_enabled',
methods=['POST'])
def toggle_enabled(service_name):
api_utils.check_service_name(service_name)
parser = ManifestParser()
try:
parser.toggle_enabled(service_name)
except NameError:
return make_response(_("'{0}' service is not "
"defined".format(service_name)), 404)
except Exception:
return make_response(_('Error toggling service enabled'), 500)
return jsonify(result='success'),
@v1_api.route('/admin/reset_caches', methods=['POST'])
def reset_caches():
api_utils.reset_cache()
return jsonify(result='success')
@v1_api.route('/admin/services/<service_name>', methods=['PUT'])
def create_or_update_service(service_name):
if not request.data:
return make_response(_('JSON data expected', 400))
try:
service_data = json.loads(request.data)
except:
return make_response(_('Unable to load json data. '
'Validate json object', 400))
service_id = service_data.get('full_service_name', service_name)
#TODO: Pass service_name instead of service_id
if not service_id or service_id != service_name:
return make_response(
_("Body attribute 'full_service_name' value is {0} which doesn't "
"correspond to 'service_name' part of URL "
"(equals to {1})".format(service_id, service_name)), 400)
resp = api_utils.create_or_update_service(service_name, service_data)
api_utils.reset_cache()
return resp