adjutant-ui/adjutant_ui/api/adjutant.py

617 lines
19 KiB
Python

# Copyright (c) 2016 Catalyst IT 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 collections
import json
import logging
import requests
from six.moves.urllib.parse import urljoin
import six
from django.conf import settings
from horizon.utils import functions as utils
from horizon.utils import memoized
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
USER = collections.namedtuple('User',
['id', 'name', 'email',
'roles', 'cohort', 'status'])
TOKEN = collections.namedtuple('Token',
['action'])
TASK = collections.namedtuple('Task',
['id', 'task_type', 'valid',
'request_by', 'request_project',
'created_on', 'approved_on', 'page',
'completed_on', 'actions', 'status'])
QUOTA_SIZE = collections.namedtuple('QuotaSize',
['id', 'name', 'cinder',
'nova', 'neutron'])
REGION_QUOTA = collections.namedtuple('RegionQuota',
['id', 'region',
'quota_size', 'preapproved_quotas'])
REGION_QUOTA_VALUE = collections.namedtuple('RegionQuotaValue',
['id', 'name',
'service', 'current_quota',
'current_usage', 'percent',
'size_blob', 'important'])
SIZE_QUOTA_VALUE = collections.namedtuple('SizeQuotaValue',
['id', 'name', 'service',
'value', 'current_quota',
'current_usage', 'percent'])
QUOTA_TASK = collections.namedtuple(
'QuotaTask',
['id', 'regions', 'size', 'user', 'created', 'valid', 'status'])
# NOTE(amelia): A list of quota names that we consider to be the most
# relevant to customers to be shown initially on the update page.
# These can be overriden in the local_settings file:
# IMPORTANT_QUOTAS = {<service>: [<quota_name>], }
DEFAULT_IMPORTANT_QUOTAS = {
'nova': [
'instances', 'cores', 'ram',
],
'cinder': [
'volumes', 'snapshots', 'gigabytes',
],
'neutron': [
'network', 'floatingip', 'router', 'security_group',
],
}
# NOTE(adriant): Quotas that should be hidden by default.
# Can be overriden in the local_settings file by setting:
# HIDDEN_QUOTAS = {<service>: [<quota_name>], }
# or disabled entirely with: HIDDEN_QUOTAS = {}
DEFAULT_HIDDEN_QUOTAS = {
# these values have long since been deprecated from Nova
'nova': [
'security_groups', 'security_group_rules',
'floating_ips', 'fixed_ips',
],
# these by default have no limit
'cinder': [
'per_volume_gigabytes', 'volumes_lvmdriver-1',
'gigabytes_lvmdriver-1', 'snapshots_lvmdriver-1',
],
'neutron': [
'subnetpool',
],
}
def _get_endpoint_url(request):
# If the request is made by an anonymous user, this endpoint request fails.
# Thus, we must hardcode this in Horizon.
if getattr(request.user, "service_catalog", None):
url = base.url_for(request, service_type='registration')
else:
url = getattr(settings, 'OPENSTACK_REGISTRATION_URL')
# Ensure ends in slash
if not url.endswith('/'):
url += '/'
return url
def _request(request, method, url, headers, **kwargs):
try:
endpoint_url = _get_endpoint_url(request)
url = urljoin(endpoint_url, url)
session = requests.Session()
data = kwargs.pop("data", None)
return session.request(method, url, headers=headers,
data=data, **kwargs)
except Exception as e:
LOG.error(e)
raise
def head(request, url, **kwargs):
return _request(request, 'HEAD', url, **kwargs)
def get(request, url, **kwargs):
return _request(request, 'GET', url, **kwargs)
def post(request, url, **kwargs):
return _request(request, 'POST', url, **kwargs)
def put(request, url, **kwargs):
return _request(request, 'PUT', url, **kwargs)
def patch(request, url, **kwargs):
return _request(request, 'PATCH', url, **kwargs)
def delete(request, url, **kwargs):
return _request(request, 'DELETE', url, **kwargs)
def user_invite(request, user):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
user['project_id'] = request.user.tenant_id
return post(request, 'openstack/users',
headers=headers, data=json.dumps(user))
def user_list(request):
users = []
try:
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
resp = json.loads(get(request, 'openstack/users',
headers=headers).content)
for user in resp['users']:
users.append(
USER(
id=user['id'],
name=user['name'],
email=user['email'],
roles=user['roles'],
status=user['status'],
cohort=user['cohort']
)
)
except Exception as e:
LOG.error(e)
raise
return users
def user_get(request, user_id):
try:
headers = {'X-Auth-Token': request.user.token.id}
resp = get(request, 'openstack/users/%s' % user_id,
headers=headers).content
return json.loads(resp)
except Exception as e:
LOG.error(e)
raise
def user_roles_update(request, user):
try:
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
user['project_id'] = request.user.tenant_id
user['roles'] = user.roles
return put(request, 'openstack/users/%s/roles' % user['id'],
headers=headers,
data=json.dumps(user))
except Exception as e:
LOG.error(e)
raise
def user_roles_add(request, user_id, roles):
try:
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
params = {}
params['project_id'] = request.user.tenant_id
params['roles'] = roles
return put(request, 'openstack/users/%s/roles' % user_id,
headers=headers,
data=json.dumps(params))
except Exception as e:
LOG.error(e)
raise
def user_roles_remove(request, user_id, roles):
try:
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
params = {}
params['project_id'] = request.user.tenant_id
params['roles'] = roles
return delete(request, 'openstack/users/%s/roles' % user_id,
headers=headers,
data=json.dumps(params))
except Exception as e:
LOG.error(e)
raise
def user_revoke(request, user_id):
try:
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
data = dict()
return delete(request, 'openstack/users/%s' % user_id,
headers=headers,
data=json.dumps(data))
except Exception as e:
LOG.error(e)
raise
def user_invitation_resend(request, user_id):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
# For non-active users, the task id is the same as their userid
# For active users, re-sending an invitation doesn't make sense.
data = {
"task": user_id
}
return post(request, 'tokens',
headers=headers,
data=json.dumps(data))
def valid_roles_get(request):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
role_data = get(request, 'openstack/roles', headers=headers)
return role_data.json()
def valid_role_names_get(request):
roles_data = valid_roles_get(request)
role_names = [r['name'] for r in roles_data['roles']]
return role_names
def token_get(request, token, data):
headers = {'Content-Type': 'application/json'}
return get(request, 'tokens/%s' % token,
data=json.dumps(data), headers=headers)
def token_submit(request, token, data):
headers = {"Content-Type": "application/json"}
return post(request, 'tokens/%s' % token,
data=json.dumps(data), headers=headers)
def token_reissue(request, task_id):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
data = {'task': task_id}
return post(request, 'tokens/',
data=json.dumps(data), headers=headers)
def email_update(request, email):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
data = {
'new_email': email
}
return post(request, 'openstack/users/email-update',
data=json.dumps(data), headers=headers)
def forgotpassword_submit(request, data):
headers = {"Content-Type": "application/json"}
try:
return post(request, 'openstack/users/password-reset',
data=json.dumps(data),
headers=headers)
except Exception as e:
LOG.error(e)
raise
def signup_submit(request, data):
headers = {"Content-Type": "application/json"}
try:
return post(request, 'openstack/sign-up',
data=json.dumps(data),
headers=headers)
except Exception as e:
LOG.error(e)
raise
def task_list(request, filters={}, page=1):
tasks_per_page = utils.get_page_size(request)
tasklist = []
prev = more = False
try:
headers = {"Content-Type": "application/json",
'X-Auth-Token': request.user.token.id}
params = {
"filters": json.dumps(filters),
"page": page,
"tasks_per_page": tasks_per_page
}
resp = get(request, "tasks", params=params, data=json.dumps({}),
headers=headers).json()
prev = resp['has_prev']
more = resp['has_more']
for task in resp['tasks']:
tasklist.append(task_obj_get(request, task=task, page=page))
return tasklist, prev, more
except Exception as e:
LOG.error(e)
raise
def task_get(request, task_id):
# Get a single task
headers = {"Content-Type": "application/json",
'X-Auth-Token': request.user.token.id}
return get(request, "tasks/%s" % task_id,
headers=headers)
def task_obj_get(request, task_id=None, task=None, page=0):
if not task:
task = task_get(request, task_id)
status = "Awaiting Approval"
if task['cancelled']:
status = "Cancelled"
elif task['completed_on']:
status = "Completed"
elif task['approved_on']:
status = "Approved; Incomplete"
valid = False not in [action['valid'] for
action in task['actions']]
return TASK(
id=task['uuid'],
task_type=task['task_type'],
valid=valid,
request_by=task['keystone_user'].get('username'),
request_project=task['keystone_user'].get('project_name'),
status=status,
created_on=task['created_on'],
approved_on=task['approved_on'],
completed_on=task['completed_on'],
actions=task['actions'],
page=page
)
def task_cancel(request, task_id):
headers = {"Content-Type": "application/json",
'X-Auth-Token': request.user.token.id}
return delete(request, "tasks/%s" % task_id,
headers=headers)
def task_approve(request, task_id):
headers = {"Content-Type": "application/json",
'X-Auth-Token': request.user.token.id}
return post(request, "tasks/%s" % task_id,
data=json.dumps({"approved": True}), headers=headers)
def task_update(request, task_id, new_data):
headers = {"Content-Type": "application/json",
'X-Auth-Token': request.user.token.id}
return put(request, "tasks/%s" % task_id,
data=new_data, headers=headers)
def task_revalidate(request, task_id):
task = task_get(request, task_id=task_id).json()
data = {}
for action_data in [action['data'] for action in task['actions']]:
data.update(action_data)
return task_update(request, task_id, json.dumps(data))
# Quota management functions
def _is_quota_hidden(service, resource):
hidden_quotas = getattr(settings, 'HIDDEN_QUOTAS', None)
if hidden_quotas is None:
hidden_quotas = DEFAULT_HIDDEN_QUOTAS
return service in hidden_quotas and resource in hidden_quotas[service]
def _is_quota_important(service, resource):
important_quotas = getattr(settings, 'IMPORTANT_QUOTAS', None)
if important_quotas is None:
important_quotas = DEFAULT_IMPORTANT_QUOTAS
return (
service in important_quotas and resource in important_quotas[service])
@memoized.memoized_method
def _get_quota_information(request, regions=None):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
params = {}
if regions:
params = {'regions': regions}
try:
return get(request, 'openstack/quotas/',
params=params, headers=headers).json()
except Exception as e:
LOG.error(e)
raise
def quota_sizes_get(request, region=None):
# Gets the list of quota sizes, and a json blob defining what they
# have for each of the services
# Region param is useless here, but nedded for memoized decorator to work
quota_sizes_dict = {}
resp = _get_quota_information(request, regions=region)
for size_name, size in six.iteritems(resp['quota_sizes']):
quota_sizes_dict[size_name] = QUOTA_SIZE(
id=size_name,
name=size_name,
cinder=json.dumps(size['cinder'], indent=1),
nova=json.dumps(size['nova'], indent=1),
neutron=json.dumps(size['neutron'], indent=1),
)
quota_sizes = []
for size in resp['quota_size_order']:
quota_sizes.append(quota_sizes_dict[size])
return quota_sizes
def size_details_get(request, size, region=None):
""" Gets the current details of the size as well as the current region's
quota
"""
quota_details = []
if not region:
region = request.user.services_region
resp = _get_quota_information(request, regions=region)
data = resp['quota_sizes'][size]
region_data = resp['regions'][0]['current_quota']
for service, values in six.iteritems(data):
for resource, value in six.iteritems(values):
if _is_quota_hidden(service, resource):
continue
usage = resp['regions'][0]['current_usage'][service].get(
resource)
try:
percent = float(usage)/value
except (TypeError, ZeroDivisionError):
percent = '-'
quota_details.append(
SIZE_QUOTA_VALUE(
id=resource,
name=resource,
service=service,
value=value,
current_quota=region_data[service][resource],
current_usage=usage,
percent=percent
)
)
return quota_details
def quota_details_get(request, region):
quota_details = []
resp = _get_quota_information(request, regions=region)
data = resp['regions'][0]['current_quota']
for service, values in six.iteritems(data):
for name, value in six.iteritems(values):
if _is_quota_hidden(service, name):
continue
if value < 0:
value = 'No Limit'
usage = resp['regions'][0]['current_usage'][service].get(name)
try:
percent = float(usage)/value
except (TypeError, ZeroDivisionError):
percent = '-'
size_blob = {}
for size_name, size_data in resp['quota_sizes'].iteritems():
size_blob[size_name] = size_data[service].get(name, '-')
if name != 'id':
quota_details.append(
REGION_QUOTA_VALUE(
id=name,
name=name,
service=service,
current_quota=value,
current_usage=usage,
percent=percent,
size_blob=size_blob,
important=_is_quota_important(service, name)
)
)
return quota_details
def region_quotas_get(request, region=None):
quota_details = []
resp = _get_quota_information(request, regions=region)
data = resp['regions']
for region_values in data:
quota_details.append(
REGION_QUOTA(
id=region_values['region'],
region=region_values['region'],
quota_size=region_values['current_quota_size'],
preapproved_quotas=', '.join(region_values[
'quota_change_options'])
)
)
return quota_details
def quota_tasks_get(request, region=None):
# Region param only used to help with memoized decorator
quota_tasks = []
resp = _get_quota_information(request, regions=region)
for task in resp['active_quota_tasks']:
quota_tasks.append(
QUOTA_TASK(
id=task['id'],
regions=', '.join(task['regions']),
size=task['size'],
user=task['request_user'],
created=task['task_created'].split("T")[0],
valid=task['valid'],
status=task['status'],
)
)
return quota_tasks
def update_quotas(request, size, regions=[]):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
data = {
'size': size,
}
if regions:
data['regions'] = regions
return post(request, 'openstack/quotas/',
data=json.dumps(data),
headers=headers)