Refinements for Signup

Reworking NewProject as a standalone and making NewProjectWithUser
for use with signup style tasks.

NewProject and NewProjectWithUser now create the project and user
at post_approve and then resets the user password at submit.
 - This change allows signup tokens to expire and a new signup to
   use the reset feature to still get access. The process still
   appears exactly the same to the end user.
 - Existing users creating a new project will also get created at
   post_approve step, but as they needed no token this
   functionality does not change from an outside perspective.

Fixing a project creation issue with keystone V3, wasn't setting
domain.

More standardisation in action handling functions.

Duplicate error now returns 409 rather than 400 for clarity.

Adding an "approved_by" values to tasks both for auditing and for
possible future logic checks.

Reworking of Network resource creation into two variant actions.

Reworking AddAdminToProject to be more generic and allow a list of
users.

Fixing issues with logic for task approval and task updating.

Change-Id: Ieba9907e5632dd441a86c41de291c6a7d0c8764a
This commit is contained in:
adrian-turjak 2016-08-12 14:53:27 +12:00 committed by Dale Smith
parent e5084a84ed
commit 57b54baabe
20 changed files with 1078 additions and 383 deletions

View File

@ -50,7 +50,7 @@ KEYSTONE:
project_name: admin
# MUST BE V3 API:
auth_url: http://localhost:5000/v3
DEFAULT_REGION: RegionOne
domain_id: default
TOKEN_SUBMISSION_URL: http://192.168.122.160:8080/token/
@ -122,15 +122,15 @@ TASK_SETTINGS:
create_project:
# You can override 'default_actions' if needed for given taskviews
# The order of the actions is order of execution.
#
#
# default_actions:
# - NewProject
#
# Additonal actions for views
# These will run after the default actions, in the given order.
additional_actions:
- AddAdminToProject
- DefaultProjectResources
- AddDefaultUsersToProject
- NewProjectDefaultNetwork
notifications:
standard:
EmailNotification:
@ -144,6 +144,10 @@ TASK_SETTINGS:
- signups@example.com
RTNotification:
queue: signups
default_region: RegionOne
# If 'None' (null in yaml), will default to domain as parent.
# If domain isn't set explicity, will service user domain (see KEYSTONE).
default_parent_id: null
invite_user:
emails:
# To not send this email, set the value to null
@ -187,6 +191,12 @@ TASK_SETTINGS:
# Action settings:
ACTION_SETTINGS:
NewProject:
default_roles:
- project_admin
- project_mod
- heat_stack_owner
- _member_
NewUser:
allowed_roles:
- project_admin
@ -196,16 +206,21 @@ ACTION_SETTINGS:
ResetUser:
blacklisted_roles:
- admin
DefaultProjectResources:
NewDefaultNetwork:
RegionOne:
network_name: somenetwork
subnet_name: somesubnet
router_name: somerouter
network_name: default_network
subnet_name: default_subnet
router_name: default_router
public_network: 3cb50d61-5bce-4c03-96e6-8e262e12bb35
DNS_NAMESERVERS:
- 193.168.1.2
- 193.168.1.3
SUBNET_CIDR: 192.168.1.0/24
AddDefaultUsersToProject:
default_users:
- admin
default_roles:
- admin
# mapping between roles and managable roles
ROLES_MAPPING:

View File

@ -13,6 +13,7 @@
# under the License.
from logging import getLogger
from uuid import uuid4
from django.conf import settings
from django.db import models
@ -379,35 +380,22 @@ class NewUser(UserNameAction):
% (self.username, self.roles, self.project_id))
class NewProject(UserNameAction):
"""
Similar functionality as the NewUser action,
but will create the project if valid. Will setup
the user (existing or new) with the 'default_role'.
"""
class ProjectCreateBase(object):
"""Mixin with functions for project creation."""
required = [
'project_name',
'username',
'email'
]
# NOTE(adriant): move these to a config somewhere?
default_roles = {
"project_admin", "project_mod", "_member_", "heat_stack_owner"
}
def _validate(self):
project_valid = self._validate_project()
user_valid = self._validate_user()
self.action.valid = project_valid and user_valid
self.action.save()
def _validate_parent_project(self):
# NOTE(adriant): If parent id is None, Keystone defaults to the domain.
# So we only care to validate if parent_id is not None.
if self.parent_id:
parent = self.id_manager.get_project(self.parent_id)
if not parent:
self.add_note("Parent id: '%s' does not exist." %
self.project_name)
return False
return True
def _validate_project(self):
id_manager = user_store.IdentityManager()
project = id_manager.find_project(self.project_name)
project = self.id_manager.find_project(self.project_name)
if project:
self.add_note("Existing project with name '%s'." %
self.project_name)
@ -417,9 +405,148 @@ class NewProject(UserNameAction):
self.project_name)
return True
def _create_project(self):
try:
project = self.id_manager.create_project(
self.project_name, created_on=str(timezone.now()),
parent=self.parent_id)
except Exception as e:
self.add_note(
"Error: '%s' while creating project: %s" %
(e, self.project_name))
raise
# put project_id into action cache:
self.action.task.cache['project_id'] = project.id
self.set_cache('project_id', project.id)
self.add_note("New project '%s' created." % project.name)
def _grant_roles(self, user, roles, project_id):
ks_roles = []
for role in roles:
ks_role = self.id_manager.find_role(role)
if ks_role:
ks_roles.append(ks_role)
else:
raise TypeError("Keystone missing role: %s" % role)
for role in ks_roles:
self.id_manager.add_user_role(user, role, project_id)
# TODO(adriant): Write tests for this action.
class NewProject(BaseAction, ProjectCreateBase):
"""
Creates a new project for the current keystone_user.
This action can only be used for an autheticated taskview.
"""
required = [
'parent_id',
'project_name',
]
def __init__(self, *args, **kwargs):
super(NewProject, self).__init__(*args, **kwargs)
self.id_manager = user_store.IdentityManager()
def _validate(self):
valid_parent = self._validate_parent_project()
valid_project = self._validate_project()
self.action.valid = valid_project and valid_parent
self.action.save()
def _validate_parent_project(self):
if self.parent_id:
keystone_user = self.action.task.keystone_user
if self.parent_id != keystone_user['project_id']:
self.add_note(
'Parent id does not match keystone user project.')
return False
return super(NewProject, self)._validate_parent_project()
return True
def _pre_approve(self):
self._validate()
def _post_approve(self):
project_id = self.get_cache('project_id')
if project_id:
self.action.task.cache['project_id'] = project_id
self.add_note("Project already created.")
else:
self._validate()
if not self.valid:
return
self._create_project()
user_id = self.get_cache('user_id')
if user_id:
self.action.task.cache['user_id'] = user_id
self.add_note("User already given roles.")
else:
default_roles = settings.ACTION_SETTINGS.get(
'NewProject', {}).get("default_roles", {})
project_id = self.get_cache('project_id')
keystone_user = self.action.task.keystone_user
try:
user = self.id_manager.get_user(keystone_user['user_id'])
self._grant_roles(user, default_roles, project_id)
except Exception as e:
self.add_note(
("Error: '%s' while adding roles %s "
"to user '%s' on project '%s'") %
(e, self.username, default_roles, project_id))
raise
# put user_id into action cache:
self.action.task.cache['user_id'] = user.id
self.set_cache('user_id', user.id)
self.add_note(("Existing user '%s' attached to project %s" +
" with roles: %s")
% (self.username, project_id,
default_roles))
def _submit(self, token_data):
"""
Nothing to do here. Everything is done at post_approve.
"""
pass
class NewProjectWithUser(UserNameAction, ProjectCreateBase):
"""
Makes a new project for the given username. Will create the user if it
doesn't exists.
"""
required = [
'parent_id',
'project_name',
'username',
'email'
]
def __init__(self, *args, **kwargs):
super(NewProjectWithUser, self).__init__(*args, **kwargs)
self.id_manager = user_store.IdentityManager()
def _validate(self):
valid_parent = self._validate_parent_project()
project_valid = self._validate_project()
user_valid = self._validate_user()
self.action.valid = valid_parent and project_valid and user_valid
self.action.save()
def _validate_user(self):
id_manager = user_store.IdentityManager()
user = id_manager.find_user(self.username)
user = self.id_manager.find_user(self.username)
if user:
if user.email == self.email:
@ -441,104 +568,135 @@ class NewProject(UserNameAction):
return valid
def _validate_user_submit(self):
user_id = self.get_cache('user_id')
project_id = self.get_cache('project_id')
user = self.id_manager.get_user(user_id)
project = self.id_manager.get_project(project_id)
if user and project:
self.action.valid = True
else:
self.action.valid = False
self.action.save()
def _pre_approve(self):
self._validate()
def _post_approve(self):
"""
Approving a registration means we set up the project itself,
and then the user registration token is valid for submission and
creating the user themselves.
Approving a new project means we set up the project itself,
and if the user doesn't exist, create it right away. An existing
user automatically gets added to the new project.
"""
project_id = self.get_cache('project_id')
if project_id:
self.action.task.cache['project_id'] = project_id
self.add_note("Project already created.")
return
else:
self.action.valid = (
self._validate_project() and self._validate_parent_project())
self.action.save()
self._validate()
if not self.valid:
return
if not self.valid:
return
id_manager = user_store.IdentityManager()
try:
project = id_manager.create_project(
self.project_name, created_on=str(timezone.now()))
except Exception as e:
self.add_note(
"Error: '%s' while creating project: %s" %
(e, self.project_name))
raise
# put project_id into action cache:
self.action.task.cache['project_id'] = project.id
self.set_cache('project_id', project.id)
self.add_note("New project '%s' created." % self.project_name)
self._create_project()
user_id = self.get_cache('user_id')
if user_id:
self.action.task.cache['user_id'] = user_id
self.add_note("User already created.")
else:
self.action.valid = self._validate_user()
self.action.save()
if not self.valid:
return
default_roles = settings.ACTION_SETTINGS.get(
'NewProject', {}).get("default_roles", {})
project_id = self.get_cache('project_id')
if self.action.state == "default":
try:
# Generate a temporary password:
password = uuid4().hex + uuid4().hex
user = self.id_manager.create_user(
name=self.username, password=password,
email=self.email, project_id=project_id)
self._grant_roles(user, default_roles, project_id)
except Exception as e:
self.add_note(
"Error: '%s' while creating user: %s with roles: %s" %
(e, self.username, default_roles))
raise
# put user_id into action cache:
self.action.task.cache['user_id'] = user.id
self.set_cache('user_id', user.id)
self.add_note(
"New user '%s' created for project %s with roles: %s" %
(self.username, project_id, default_roles))
elif self.action.state == "existing":
try:
user = self.id_manager.find_user(self.username)
self._grant_roles(user, default_roles, project_id)
except Exception as e:
self.add_note(
"Error: '%s' while attaching user: %s with roles: %s" %
(e, self.username, default_roles))
raise
# put user_id into action cache:
self.action.task.cache['user_id'] = user.id
self.set_cache('user_id', user.id)
self.add_note(("Existing user '%s' attached to project %s" +
" with roles: %s")
% (self.username, project_id,
default_roles))
def _submit(self, token_data):
"""
The submit action is prformed when a token is submitted.
This is done for a user account only, and so should now only
set up the user, not the project, which was done in approve.
The submit action is performed when a token is submitted.
This is done to set a user password only, and so should now only
change the user password. The project and user themselves are created
on post_approve.
"""
id_manager = user_store.IdentityManager()
self.action.valid = self._validate_user()
self.action.save()
self._validate_user_submit()
if not self.valid:
return
project_id = self.get_cache('project_id')
self.action.task.cache['project_id'] = project_id
project = id_manager.get_project(project_id)
user_id = self.get_cache('user_id')
self.action.task.cache['user_id'] = user_id
if self.action.state == "default":
user = self.id_manager.get_user(user_id)
try:
roles = []
for role in self.default_roles:
ks_role = id_manager.find_role(role)
if ks_role:
roles.append(ks_role)
else:
raise TypeError("Keystone missing role: %s" % role)
user = id_manager.create_user(
name=self.username, password=token_data['password'],
email=self.email, project_id=project.id)
for role in roles:
id_manager.add_user_role(user, role, project.id)
self.id_manager.update_user_password(
user, token_data['password'])
except Exception as e:
self.add_note(
"Error: '%s' while creating user: %s with roles: %s" %
(e, self.username, self.default_roles))
"Error: '%s' while changing password for user: %s" %
(e, self.username))
raise
self.add_note('User %s password has been changed.' % self.username)
self.add_note(
"New user '%s' created for project %s with roles: %s" %
(self.username, self.project_name, self.default_roles))
elif self.action.state == "existing":
try:
user = id_manager.find_user(self.username)
roles = []
for role in self.default_roles:
roles.append(id_manager.find_role(role))
for role in roles:
id_manager.add_user_role(user, role, project.id)
except Exception as e:
self.add_note(
"Error: '%s' while attaching user: %s with roles: %s" %
(e, self.username, self.default_roles))
raise
self.add_note(("Existing user '%s' attached to project %s" +
" with roles: %s")
% (self.username, self.project_name,
self.default_roles))
# do nothing, everything is already done.
self.add_note(
"Existing user '%s' already attached to project %s" % (
user_id, project_id))
class ResetUser(UserNameAction):
@ -761,6 +919,7 @@ def register_action_class(action_class, serializer_class):
# Register each action model
register_action_class(NewUser, serializers.NewUserSerializer)
register_action_class(NewProject, serializers.NewProjectSerializer)
register_action_class(
NewProjectWithUser, serializers.NewProjectWithUserSerializer)
register_action_class(ResetUser, serializers.ResetUserSerializer)
register_action_class(EditUserRoles, serializers.EditUserSerializer)

View File

@ -29,21 +29,23 @@ def get_keystoneclient():
password=settings.KEYSTONE['password'],
project_name=settings.KEYSTONE['project_name'],
auth_url=settings.KEYSTONE['auth_url'],
user_domain_name="default",
project_domain_name="default",
user_domain_id=settings.KEYSTONE.get('domain_id', "default"),
project_domain_id=settings.KEYSTONE.get('domain_id', "default"),
)
sess = session.Session(auth=auth)
auth = ks_client.Client(session=sess)
return auth
def get_neutronclient():
# TODO(Adriant): Add region support.
neutron = neutron_client.Client(
def get_neutronclient(region):
auth = v3.Password(
username=settings.KEYSTONE['username'],
password=settings.KEYSTONE['password'],
tenant_name=settings.KEYSTONE['project_name'],
project_name=settings.KEYSTONE['project_name'],
auth_url=settings.KEYSTONE['auth_url'],
region_name=settings.DEFAULT_REGION
user_domain_id=settings.KEYSTONE.get('domain_id', "default"),
project_domain_id=settings.KEYSTONE.get('domain_id', "default"),
)
sess = session.Session(auth=auth)
neutron = neutron_client.Client(session=sess, region_name=region)
return neutron

View File

@ -44,7 +44,15 @@ class NewUserSerializer(BaseUserNameSerializer):
pass
class NewProjectSerializer(BaseUserNameSerializer):
class NewProjectSerializer(serializers.Serializer):
parent_id = serializers.CharField(
max_length=200, default=None, allow_null=True)
project_name = serializers.CharField(max_length=200)
class NewProjectWithUserSerializer(BaseUserNameSerializer):
parent_id = serializers.CharField(
max_length=200, default=None, allow_null=True)
project_name = serializers.CharField(max_length=200)

View File

@ -13,13 +13,14 @@
# under the License.
from stacktask.actions.models import BaseAction
from stacktask.actions.tenant_setup.serializers import DefaultProjectResourcesSerializer
from stacktask.actions.tenant_setup.serializers import (
NewDefaultNetworkSerializer, NewProjectDefaultNetworkSerializer)
from django.conf import settings
from stacktask.actions.user_store import IdentityManager
from stacktask.actions import openstack_clients
class DefaultProjectResources(BaseAction):
class NewDefaultNetwork(BaseAction):
"""
This action will setup all required basic networking
resources so that a new user can launch instances
@ -27,36 +28,62 @@ class DefaultProjectResources(BaseAction):
"""
required = [
'setup_resources'
'setup_network',
'project_id',
'region',
]
region = settings.DEFAULT_REGION
defaults = settings.ACTION_SETTINGS['DefaultProjectResources'][region]
def _validate(self):
project_id = self.action.task.cache.get('project_id', None)
# Default state is invalid
self.action.valid = False
valid = False
if project_id:
valid = True
self.add_note('project_id given: %s' % project_id)
else:
if not self.project_id:
self.add_note('No project_id given.')
return valid
return
def _setup_resources(self):
neutron = openstack_clients.get_neutronclient()
if not self.region:
self.add_note('No region given.')
return
project_id = self.action.task.cache['project_id']
keystone_user = self.action.task.keystone_user
if keystone_user.get('project_id') != self.project_id:
self.add_note('Project id does not match keystone user project.')
return
id_manager = IdentityManager()
project = id_manager.get_project(self.project_id)
if not project:
self.add_note('Project does not exist.')
return
self.add_note('Project_id: %s exists.' % project.id)
region = id_manager.find_region(self.region)
if not region:
self.add_note('Region does not exist.')
return
self.add_note('Region: %s exists.' % self.region)
self.defaults = settings.ACTION_SETTINGS.get(
'NewDefaultNetwork', {}).get(self.region, {})
if not self.defaults:
self.add_note('ERROR: No default settings for given region.')
return
self.action.valid = True
def _create_network(self):
neutron = openstack_clients.get_neutronclient(region=self.region)
if not self.get_cache('network_id'):
try:
network_body = {
"network": {
"name": self.defaults['network_name'],
'tenant_id': project_id,
'tenant_id': self.project_id,
"admin_state_up": True
}
}
@ -69,11 +96,11 @@ class DefaultProjectResources(BaseAction):
self.set_cache('network_id', network['network']['id'])
self.add_note("Network %s created for project %s" %
(self.defaults['network_name'],
self.action.task.cache['project_id']))
self.project_id))
else:
self.add_note("Network %s already created for project %s" %
(self.defaults['network_name'],
self.action.task.cache['project_id']))
self.project_id))
if not self.get_cache('subnet_id'):
try:
@ -81,7 +108,7 @@ class DefaultProjectResources(BaseAction):
"subnet": {
"network_id": self.get_cache('network_id'),
"ip_version": 4,
'tenant_id': project_id,
'tenant_id': self.project_id,
'dns_nameservers': self.defaults['DNS_NAMESERVERS'],
"cidr": self.defaults['SUBNET_CIDR']
}
@ -106,7 +133,7 @@ class DefaultProjectResources(BaseAction):
"external_gateway_info": {
"network_id": self.defaults['public_network']
},
'tenant_id': project_id,
'tenant_id': self.project_id,
"admin_state_up": True
}
}
@ -118,10 +145,10 @@ class DefaultProjectResources(BaseAction):
raise
self.set_cache('router_id', router['router']['id'])
self.add_note("Router created for project %s" %
self.action.task.cache['project_id'])
self.project_id)
else:
self.add_note("Router already created for project %s" %
self.action.task.cache['project_id'])
self.project_id)
try:
interface_body = {
@ -136,76 +163,207 @@ class DefaultProjectResources(BaseAction):
self.add_note("Interface added to router for subnet")
def _pre_approve(self):
# Not exactly valid, but not exactly invalid.
self.action.valid = True
self._validate()
self.action.save()
def _post_approve(self):
self.action.valid = self._validate()
self._validate()
self.action.save()
if self.setup_resources and self.valid:
self._setup_resources()
if self.setup_network and self.valid:
self._create_network()
def _submit(self, token_data):
pass
class AddAdminToProject(BaseAction):
class NewProjectDefaultNetwork(NewDefaultNetwork):
"""
Action to add 'admin' user to project for
monitoring purposes.
A variant of NewDefaultNetwork that expects the project
to not be created until after post_approve.
"""
required = [
'setup_network',
'region',
]
def _pre_validate(self):
# Default state is invalid
self.action.valid = False
# We don't check project here as it doesn't exist yet.
if not self.region:
self.add_note('No region given.')
return
id_manager = IdentityManager()
region = id_manager.find_region(self.region)
if not region:
self.add_note('Region does not exist.')
return
self.add_note('Region: %s exists.' % self.region)
self.defaults = settings.ACTION_SETTINGS.get(
'NewDefaultNetwork', {}).get(self.region, {})
if not self.defaults:
self.add_note('ERROR: No default settings for given region.')
return
self.action.valid = True
def _validate(self):
project_id = self.action.task.cache.get('project_id', None)
# Default state is invalid
self.action.valid = False
valid = False
if project_id:
valid = True
self.add_note('project_id given: %s' % project_id)
else:
self.project_id = self.action.task.cache.get('project_id', None)
if not self.project_id:
self.add_note('No project_id given.')
return valid
return
if not self.region:
self.add_note('No region given.')
return
id_manager = IdentityManager()
project = id_manager.get_project(self.project_id)
if not project:
self.add_note('Project does not exist.')
return
self.add_note('Project_id: %s exists.' % project.id)
region = id_manager.find_region(self.region)
if not region:
self.add_note('Region does not exist.')
return
self.add_note('Region: %s exists.' % self.region)
self.defaults = settings.ACTION_SETTINGS.get(
'NewDefaultNetwork', {}).get(self.region, {})
if not self.defaults:
self.add_note('ERROR: No default settings for given region.')
return
self.action.valid = True
def _pre_approve(self):
# Not yet exactly valid, but not exactly invalid.
self.action.valid = True
self._pre_validate()
self.action.save()
def _post_approve(self):
self.action.valid = self._validate()
self._validate()
self.action.save()
if self.setup_network and self.valid:
self._create_network()
class AddDefaultUsersToProject(BaseAction):
"""
The purpose of this action is to add a given set of users after
the creation of a new Project. This is mainly for administrative
purposes, and for users involved with migrations, monitoring, and
general admin tasks that should de present by default.
"""
def _validate_users(self):
self.users = settings.ACTION_SETTINGS.get(
'AddDefaultUsersToProject', {}).get('default_users', [])
self.roles = settings.ACTION_SETTINGS.get(
'AddDefaultUsersToProject', {}).get('default_roles', [])
id_manager = IdentityManager()
all_found = True
for user in self.users:
ks_user = id_manager.find_user(user)
if ks_user:
self.add_note('User: %s exists.' % user)
else:
self.add_note('ERROR: User: %s does not exist.' % user)
all_found = False
for role in self.roles:
ks_role = id_manager.find_role(role)
if ks_role:
self.add_note('Role: %s exists.' % role)
else:
self.add_note('ERROR: Role: %s does not exist.' % role)
all_found = False
if all_found:
return True
else:
return False
def _validate_project(self):
self.project_id = self.action.task.cache.get('project_id', None)
id_manager = IdentityManager()
project = id_manager.get_project(self.project_id)
if not project:
self.add_note('Project does not exist.')
return False
self.add_note('Project_id: %s exists.' % project.id)
return True
def _validate(self):
if self._validate_users() and self._validate_project():
self.action.valid = True
else:
self.action.valid = False
self.action.save()
def _pre_approve(self):
self.action.valid = self._validate_users()
self.action.save()
def _post_approve(self):
self._validate()
if self.valid and not self.action.state == "completed":
id_manager = IdentityManager()
project = id_manager.get_project(
self.action.task.cache['project_id'])
project = id_manager.get_project(self.project_id)
try:
user = id_manager.find_user(name="admin")
role = id_manager.find_role(name="admin")
id_manager.add_user_role(user, role, project.id)
for user in self.users:
ks_user = id_manager.find_user(name=user)
for role in self.roles:
ks_role = id_manager.find_role(name=role)
id_manager.add_user_role(ks_user, ks_role, project.id)
self.add_note(
'User: "%s" given role: %s on project: %s.' %
(ks_user.name, ks_role.name, project.id))
except Exception as e:
self.add_note(
"Error: '%s' while adding admin to project: %s" %
"Error: '%s' while adding users to project: %s" %
(e, project.id))
raise
self.action.state = "completed"
self.action.save()
self.add_note(
'Admin has been added to %s.' %
self.action.task.cache['project_id'])
self.add_note("All users added.")
def _submit(self, token_data):
pass
action_classes = {
'DefaultProjectResources': (DefaultProjectResources,
DefaultProjectResourcesSerializer),
'AddAdminToProject': (AddAdminToProject, None)
'NewDefaultNetwork': (
NewDefaultNetwork, NewDefaultNetworkSerializer),
'NewProjectDefaultNetwork': (
NewProjectDefaultNetwork, NewProjectDefaultNetworkSerializer),
'AddDefaultUsersToProject': (AddDefaultUsersToProject, None)
}

View File

@ -15,5 +15,12 @@
from rest_framework import serializers
class DefaultProjectResourcesSerializer(serializers.Serializer):
setup_resources = serializers.BooleanField(default=False)
class NewDefaultNetworkSerializer(serializers.Serializer):
setup_network = serializers.BooleanField(default=True)
project_id = serializers.CharField(max_length=100)
region = serializers.CharField(max_length=100)
class NewProjectDefaultNetworkSerializer(serializers.Serializer):
setup_network = serializers.BooleanField(default=False)
region = serializers.CharField(max_length=100)

View File

@ -17,7 +17,7 @@ from django.test import TestCase
import mock
from stacktask.actions.tenant_setup.models import (
AddAdminToProject, DefaultProjectResources)
NewDefaultNetwork, NewProjectDefaultNetwork, AddDefaultUsersToProject)
from stacktask.api.models import Task
from stacktask.api.v1 import tests
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
@ -69,32 +69,44 @@ def setup_neutron_cache():
}
def get_fake_neutron():
def get_fake_neutron(region):
return FakeNeutronClient()
class TenantSetupActionTests(TestCase):
class ProjectSetupActionTests(TestCase):
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
@mock.patch('stacktask.actions.tenant_setup.models.openstack_clients.get_neutronclient',
get_fake_neutron)
def test_resource_setup(self):
@mock.patch(
'stacktask.actions.tenant_setup.models.' +
'openstack_clients.get_neutronclient',
get_fake_neutron)
def test_network_setup(self):
"""
Base case, setup resources, no issues.
Base case, setup a new network , no issues.
"""
setup_neutron_cache()
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
ip_address="0.0.0.0",
keystone_user={
'roles': ['admin'],
'project_id': 'test_project_id'})
task.cache = {'project_id': "1"}
project = mock.Mock()
project.id = 'test_project_id'
project.name = 'test_project'
project.roles = {}
setup_temp_cache({'test_project': project}, {})
data = {
'setup_resources': True,
'setup_network': True,
'region': 'RegionOne',
'project_id': 'test_project_id',
}
action = DefaultProjectResources(data, task=task,
order=1)
action = NewDefaultNetwork(
data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
@ -116,56 +128,36 @@ class TenantSetupActionTests(TestCase):
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
@mock.patch('stacktask.actions.tenant_setup.models.openstack_clients.get_neutronclient',
get_fake_neutron)
def test_resource_setup_no_id(self):
"""
No project id given, should do nothing.
"""
setup_neutron_cache()
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
data = {
'setup_resources': True,
}
action = DefaultProjectResources(data, task=task,
order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
action.post_approve()
self.assertEquals(action.valid, False)
self.assertEquals(action.action.cache, {})
global neutron_cache
self.assertEquals(len(neutron_cache['networks']), 0)
self.assertEquals(len(neutron_cache['routers']), 0)
self.assertEquals(len(neutron_cache['subnets']), 0)
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
@mock.patch('stacktask.actions.tenant_setup.models.openstack_clients.get_neutronclient',
get_fake_neutron)
def test_resource_setup_no_setup(self):
@mock.patch(
'stacktask.actions.tenant_setup.models.' +
'openstack_clients.get_neutronclient',
get_fake_neutron)
def test_network_setup_no_setup(self):
"""
Told not to setup, should do nothing.
"""
setup_neutron_cache()
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
ip_address="0.0.0.0",
keystone_user={
'roles': ['admin'],
'project_id': 'test_project_id'})
project = mock.Mock()
project.id = 'test_project_id'
project.name = 'test_project'
project.roles = {}
setup_temp_cache({'test_project': project}, {})
data = {
'setup_resources': False,
'setup_network': False,
'region': 'RegionOne',
'project_id': 'test_project_id',
}
task.cache = {'project_id': "1"}
action = DefaultProjectResources(data, task=task,
order=1)
action = NewDefaultNetwork(
data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
@ -182,25 +174,37 @@ class TenantSetupActionTests(TestCase):
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
@mock.patch('stacktask.actions.tenant_setup.models.openstack_clients.get_neutronclient',
get_fake_neutron)
def test_resource_setup_fail(self):
@mock.patch(
'stacktask.actions.tenant_setup.models.' +
'openstack_clients.get_neutronclient',
get_fake_neutron)
def test_network_setup_fail(self):
"""
Should fail, but on re_approve will continue where it left off.
"""
setup_neutron_cache()
global neutron_cache
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
ip_address="0.0.0.0",
keystone_user={
'roles': ['admin'],
'project_id': 'test_project_id'})
project = mock.Mock()
project.id = 'test_project_id'
project.name = 'test_project'
project.roles = {}
setup_temp_cache({'test_project': project}, {})
data = {
'setup_resources': True,
'setup_network': True,
'region': 'RegionOne',
'project_id': 'test_project_id',
}
task.cache = {'project_id': "1"}
action = DefaultProjectResources(data, task=task,
order=1)
action = NewDefaultNetwork(
data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
@ -240,9 +244,216 @@ class TenantSetupActionTests(TestCase):
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
def test_add_admin(self):
@mock.patch(
'stacktask.actions.tenant_setup.models.' +
'openstack_clients.get_neutronclient',
get_fake_neutron)
def test_new_project_network_setup(self):
"""
Base case, setup network after a new project, no issues.
"""
setup_neutron_cache()
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
data = {
'setup_network': True,
'region': 'RegionOne',
}
action = NewProjectDefaultNetwork(
data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
# Now we add the project data as this is where the project
# would be created:
project = mock.Mock()
project.id = 'test_project_id'
project.name = 'test_project'
project.roles = {}
setup_temp_cache({'test_project': project}, {})
task.cache = {'project_id': "test_project_id"}
action.post_approve()
self.assertEquals(action.valid, True)
self.assertEquals(
action.action.cache,
{'network_id': 'net_id_0',
'router_id': 'router_id_2',
'subnet_id': 'subnet_id_1'}
)
global neutron_cache
self.assertEquals(len(neutron_cache['networks']), 1)
self.assertEquals(len(neutron_cache['routers']), 1)
self.assertEquals(len(neutron_cache['subnets']), 1)
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
@mock.patch(
'stacktask.actions.tenant_setup.models.' +
'openstack_clients.get_neutronclient',
get_fake_neutron)
def test_new_project_network_setup_no_id(self):
"""
No project id given, should do nothing.
"""
setup_neutron_cache()
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
data = {
'setup_network': True,
'region': 'RegionOne',
}
action = NewProjectDefaultNetwork(
data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
action.post_approve()
self.assertEquals(action.valid, False)
self.assertEquals(action.action.cache, {})
global neutron_cache
self.assertEquals(len(neutron_cache['networks']), 0)
self.assertEquals(len(neutron_cache['routers']), 0)
self.assertEquals(len(neutron_cache['subnets']), 0)
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
@mock.patch(
'stacktask.actions.tenant_setup.models.' +
'openstack_clients.get_neutronclient',
get_fake_neutron)
def test_new_project_network_setup_no_setup(self):
"""
Told not to setup, should do nothing.
"""
setup_neutron_cache()
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
data = {
'setup_network': False,
'region': 'RegionOne',
}
action = NewProjectDefaultNetwork(
data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
# Now we add the project data as this is where the project
# would be created:
project = mock.Mock()
project.id = 'test_project_id'
project.name = 'test_project'
project.roles = {}
setup_temp_cache({'test_project': project}, {})
task.cache = {'project_id': "test_project_id"}
action.post_approve()
self.assertEquals(action.valid, True)
self.assertEquals(action.action.cache, {})
global neutron_cache
self.assertEquals(len(neutron_cache['networks']), 0)
self.assertEquals(len(neutron_cache['routers']), 0)
self.assertEquals(len(neutron_cache['subnets']), 0)
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
@mock.patch(
'stacktask.actions.tenant_setup.models.' +
'openstack_clients.get_neutronclient',
get_fake_neutron)
def test_new_project_network_setup_fail(self):
"""
Should fail, but on re_approve will continue where it left off.
"""
setup_neutron_cache()
global neutron_cache
task = Task.objects.create(
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
data = {
'setup_network': True,
'region': 'RegionOne',
}
action = NewProjectDefaultNetwork(
data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
neutron_cache['routers'] = []
# Now we add the project data as this is where the project
# would be created:
project = mock.Mock()
project.id = 'test_project_id'
project.name = 'test_project'
project.roles = {}
setup_temp_cache({'test_project': project}, {})
task.cache = {'project_id': "test_project_id"}
try:
action.post_approve()
self.fail("Shouldn't get here.")
except Exception:
pass
self.assertEquals(
action.action.cache,
{'network_id': 'net_id_0',
'subnet_id': 'subnet_id_1'}
)
self.assertEquals(len(neutron_cache['networks']), 1)
self.assertEquals(len(neutron_cache['subnets']), 1)
self.assertEquals(len(neutron_cache['routers']), 0)
neutron_cache['routers'] = {}
action.post_approve()
self.assertEquals(
action.action.cache,
{'network_id': 'net_id_0',
'router_id': 'router_id_2',
'subnet_id': 'subnet_id_1'}
)
self.assertEquals(len(neutron_cache['networks']), 1)
self.assertEquals(len(neutron_cache['routers']), 1)
self.assertEquals(len(neutron_cache['subnets']), 1)
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
def test_add_default_users(self):
"""
Base case, adds admin user with admin role to project.
NOTE(adriant): both the lists of users, and the roles to add
come from test_settings. This test assumes the conf setting of:
default_users = ['admin']
default_roles = ['admin']
"""
project = mock.Mock()
project.id = 'test_project_id'
@ -256,7 +467,7 @@ class TenantSetupActionTests(TestCase):
task.cache = {'project_id': "test_project_id"}
action = AddAdminToProject({}, task=task, order=1)
action = AddDefaultUsersToProject({}, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
@ -285,7 +496,7 @@ class TenantSetupActionTests(TestCase):
task.cache = {'project_id': "test_project_id"}
action = AddAdminToProject({}, task=task, order=1)
action = AddDefaultUsersToProject({}, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)

View File

@ -17,7 +17,7 @@ from django.test import TestCase
import mock
from stacktask.actions.models import (
EditUserRoles, NewProject, NewUser, ResetUser)
EditUserRoles, NewProjectWithUser, NewUser, ResetUser)
from stacktask.api.models import Task
from stacktask.api.v1 import tests
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
@ -201,8 +201,8 @@ class ActionTests(TestCase):
"""
Base case, no project, no user.
Project created at post_approve step,
user at submit step.
Project and user created at post_approve step,
user password at submit step.
"""
setup_temp_cache({}, {})
@ -213,11 +213,12 @@ class ActionTests(TestCase):
'project_id': 'test_project_id'})
data = {
'parent_id': None,
'email': 'test@example.com',
'project_name': 'test_project',
}
action = NewProject(data, task=task, order=1)
action = NewProjectWithUser(data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
@ -227,7 +228,9 @@ class ActionTests(TestCase):
self.assertEquals(
tests.temp_cache['projects']['test_project'].name,
'test_project')
self.assertEquals(task.cache, {'project_id': "project_id_1"})
self.assertEquals(
task.cache,
{'project_id': 'project_id_1', 'user_id': 'user_id_1'})
token_data = {'password': '123456'}
action.submit(token_data)
@ -257,11 +260,12 @@ class ActionTests(TestCase):
'project_id': 'test_project_id'})
data = {
'parent_id': None,
'email': 'test@example.com',
'project_name': 'test_project',
}
action = NewProject(data, task=task, order=1)
action = NewProjectWithUser(data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
@ -271,14 +275,18 @@ class ActionTests(TestCase):
self.assertEquals(
tests.temp_cache['projects']['test_project'].name,
'test_project')
self.assertEquals(task.cache, {'project_id': "project_id_1"})
self.assertEquals(
task.cache,
{'project_id': 'project_id_1', 'user_id': 'user_id_1'})
action.post_approve()
self.assertEquals(action.valid, True)
self.assertEquals(
tests.temp_cache['projects']['test_project'].name,
'test_project')
self.assertEquals(task.cache, {'project_id': "project_id_1"})
self.assertEquals(
task.cache,
{'project_id': 'project_id_1', 'user_id': 'user_id_1'})
token_data = {'password': '123456'}
action.submit(token_data)
@ -301,7 +309,7 @@ class ActionTests(TestCase):
"""
user = mock.Mock()
user.id = 'user_id'
user.id = 'user_id_1'
user.name = "test@example.com"
user.email = "test@example.com"
@ -313,11 +321,12 @@ class ActionTests(TestCase):
'project_id': 'test_project_id'})
data = {
'parent_id': None,
'email': 'test@example.com',
'project_name': 'test_project',
}
action = NewProject(data, task=task, order=1)
action = NewProjectWithUser(data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, True)
@ -327,7 +336,9 @@ class ActionTests(TestCase):
self.assertEquals(
tests.temp_cache['projects']['test_project'].name,
'test_project')
self.assertEquals(task.cache, {'project_id': "project_id_1"})
self.assertEquals(
task.cache,
{'project_id': 'project_id_1', 'user_id': 'user_id_1'})
token_data = {'password': '123456'}
action.submit(token_data)
@ -362,11 +373,12 @@ class ActionTests(TestCase):
'project_id': 'test_project_id'})
data = {
'parent_id': None,
'email': 'test@example.com',
'project_name': 'test_project',
}
action = NewProject(data, task=task, order=1)
action = NewProjectWithUser(data, task=task, order=1)
action.pre_approve()
self.assertEquals(action.valid, False)

View File

@ -152,7 +152,32 @@ class IdentityManager(object):
project = None
return project
def create_project(self, project_name, created_on):
project = self.ks_client.projects.create(project_name,
created_on=created_on)
def update_project(self, project, name=None, domain=None, description=None,
enabled=None, **kwargs):
try:
return self.ks_client.projects.update(
project=project, domain=domain, name=name,
description=description, enabled=enabled,
**kwargs)
except ks_exceptions.NotFound:
return None
def create_project(
self, project_name, created_on, parent=None, domain="default"):
project = self.ks_client.projects.create(
project_name, domain, parent=parent, created_on=created_on)
return project
def find_region(self, region_name):
try:
region = self.ks_client.regions.find(name=region_name)
except ks_exceptions.NotFound:
region = None
return region
def get_region(self, region_id):
try:
region = self.ks_client.regions.get(region_id)
except ks_exceptions.NotFound:
region = None
return region

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('api', '0002_auto_20160815_2249'),
]
operations = [
migrations.AddField(
model_name='task',
name='approved_by',
field=jsonfield.fields.JSONField(default={}),
),
]

View File

@ -37,6 +37,9 @@ class Task(models.Model):
keystone_user = JSONField(default={})
project_id = models.CharField(max_length=32, db_index=True, null=True)
# keystone_user for the approver:
approved_by = JSONField(default={})
# type of the task, for easy grouping
task_type = models.CharField(max_length=100, db_index=True)
@ -81,6 +84,7 @@ class Task(models.Model):
"uuid": self.uuid,
"ip_address": self.ip_address,
"keystone_user": self.keystone_user,
"approved_by": self.approved_by,
"project_id": self.project_id,
"actions": actions,
"task_type": self.task_type,

View File

@ -194,18 +194,18 @@ class UserRoles(tasks.TaskView):
request.data['user_id'] = user_id
self.logger.info("(%s) - New EditUserRoles request." % timezone.now())
processed = self.process_actions(request)
processed, status = self.process_actions(request)
errors = processed.get('errors', None)
if errors:
self.logger.info("(%s) - Validation errors with registration." %
timezone.now())
return Response(errors, status=400)
return Response(errors, status=status)
task = processed['task']
self.logger.info("(%s) - AutoApproving EditUserRoles request."
% timezone.now())
response_dict, status = self.approve(task)
response_dict, status = self.approve(request, task)
add_task_id_for_roles(request, processed, response_dict, ['admin'])
@ -223,18 +223,18 @@ class UserRoles(tasks.TaskView):
request.data['user_id'] = user_id
self.logger.info("(%s) - New EditUser request." % timezone.now())
processed = self.process_actions(request)
processed, status = self.process_actions(request)
errors = processed.get('errors', None)
if errors:
self.logger.info("(%s) - Validation errors with registration." %
timezone.now())
return Response(errors, status=400)
return Response(errors, status=status)
task = processed['task']
self.logger.info("(%s) - AutoApproving EditUser request."
% timezone.now())
response_dict, status = self.approve(task)
response_dict, status = self.approve(request, task)
add_task_id_for_roles(request, processed, response_dict, ['admin'])

View File

@ -110,7 +110,7 @@ class TaskView(APIViewWithLogger):
for action in action_list:
if action['serializer'] is not None:
errors.update(action['serializer'].errors)
return {'errors': errors}
return {'errors': errors}, 400
hash_key = create_task_hash(self.task_type, action_list)
duplicate_tasks = Task.objects.filter(
@ -131,7 +131,9 @@ class TaskView(APIViewWithLogger):
self.logger.info(
"(%s) - Task is a duplicate - Ignoring new task." %
timezone.now())
return {'errors': ['Task is a duplicate of an existing task']}
return (
{'errors': ['Task is a duplicate of an existing task']},
409)
ip_address = request.META['REMOTE_ADDR']
keystone_user = request.keystone_user
@ -182,22 +184,27 @@ class TaskView(APIViewWithLogger):
["Error: Something went wrong on the server. " +
"It will be looked into shortly."]
}
return response_dict
return response_dict, 200
# send initial conformation email:
email_conf = class_conf.get('emails', {}).get('initial', None)
send_email(task, email_conf)
return {'task': task}
return {'task': task}, 200
def approve(self, task):
def approve(self, request, task):
"""
Approves the task and runs the post_approve steps.
Will create a token if required, otherwise will run the
submit steps.
"""
# We approve the task before running actions,
# that way if something goes wrong we know if it was approved,
# when it was approved, and who approved it.
task.approved = True
task.approved_on = timezone.now()
task.approved_by = request.keystone_user
task.save()
action_models = task.actions
@ -320,11 +327,11 @@ class CreateProject(TaskView):
task_type = "create_project"
default_actions = ["NewProject", ]
default_actions = ["NewProjectWithUser", ]
def post(self, request, format=None):
"""
Unauthenticated endpoint bound primarily to NewProject.
Unauthenticated endpoint bound primarily to NewProjectWithUser.
This process requires approval, so this will validate
incoming data and create a task to be approved
@ -332,7 +339,16 @@ class CreateProject(TaskView):
"""
self.logger.info("(%s) - Starting new project task." %
timezone.now())
processed = self.process_actions(request)
class_conf = settings.TASK_SETTINGS.get(self.task_type, {})
# we need to set the region the resources will be created in:
request.data['region'] = class_conf.get('default_region')
# parent_id for new project, if null defaults to domain:
request.data['parent_id'] = class_conf.get('default_parent_id')
processed, status = self.process_actions(request)
errors = processed.get('errors', None)
if errors:
@ -351,7 +367,7 @@ class CreateProject(TaskView):
add_task_id_for_roles(request, processed, response_dict, ['admin'])
return Response(response_dict, status=200)
return Response(response_dict, status=status)
class InviteUser(TaskView):
@ -383,19 +399,19 @@ class InviteUser(TaskView):
# TODO: First check if the user already exists or is pending
# We should not allow duplicate invites.
processed = self.process_actions(request)
processed, status = self.process_actions(request)
errors = processed.get('errors', None)
if errors:
self.logger.info("(%s) - Validation errors with task." %
timezone.now())
return Response(errors, status=400)
return Response(errors, status=status)
task = processed['task']
self.logger.info("(%s) - AutoApproving AttachUser request."
% timezone.now())
response_dict, status = self.approve(task)
response_dict, status = self.approve(request, task)
add_task_id_for_roles(request, processed, response_dict, ['admin'])
@ -432,19 +448,19 @@ class ResetPassword(TaskView):
"""
self.logger.info("(%s) - New ResetUser request." % timezone.now())
processed = self.process_actions(request)
processed, status = self.process_actions(request)
errors = processed.get('errors', None)
if errors:
self.logger.info("(%s) - Validation errors with task." %
timezone.now())
return Response(errors, status=400)
return Response(errors, status=status)
task = processed['task']
self.logger.info("(%s) - AutoApproving Resetuser request."
% timezone.now())
self.approve(task)
self.approve(request, task)
response_dict = {'notes': [
"If user with email exists, reset token will be issued."]}
@ -513,18 +529,18 @@ class EditUser(TaskView):
post_approve validation, and creates a Token if valid.
"""
self.logger.info("(%s) - New EditUser request." % timezone.now())
processed = self.process_actions(request)
processed, status = self.process_actions(request)
errors = processed.get('errors', None)
if errors:
self.logger.info("(%s) - Validation errors with task." %
timezone.now())
return Response(errors, status=400)
return Response(errors, status=status)
task = processed['task']
self.logger.info("(%s) - AutoApproving EditUser request."
% timezone.now())
response_dict, status = self.approve(task)
response_dict, status = self.approve(request, task)
add_task_id_for_roles(request, processed, response_dict, ['admin'])

View File

@ -27,6 +27,10 @@ def setup_temp_cache(projects, users):
users.update({admin_user.id: admin_user})
region_one = mock.Mock()
region_one.id = 'region_id_0'
region_one.name = 'RegionOne'
global temp_cache
temp_cache = {
@ -39,6 +43,9 @@ def setup_temp_cache(projects, users):
'project_admin': 'project_admin',
'project_mod': 'project_mod',
'heat_stack_owner': 'heat_stack_owner'
},
'regions': {
'RegionOne': region_one,
}
}
@ -165,7 +172,7 @@ class FakeManager(object):
if project.id == project_id:
return FakeProject(project)
def create_project(self, project_name, created_on, p_id=None):
def create_project(self, project_name, created_on, parent=None, p_id=None):
global temp_cache
project = mock.Mock()
if p_id:
@ -174,6 +181,18 @@ class FakeManager(object):
temp_cache['i'] += 0.5
project.id = "project_id_%s" % int(temp_cache['i'])
project.name = project_name
# TODO(adriant): Do something better with the parent value.
project.parent = parent
project.roles = {}
temp_cache['projects'][project_name] = project
return project
def find_region(self, region_name):
global temp_cache
return temp_cache['regions'].get(region_name, None)
def get_region(self, region_id):
global temp_cache
for region in temp_cache['regions'].values():
if region.id == region_id:
return region

View File

@ -59,6 +59,8 @@ class AdminAPITests(APITestCase):
@mock.patch(
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
def test_task_get(self):
"""
Test the basic task detail view.
@ -260,7 +262,11 @@ class AdminAPITests(APITestCase):
headers=headers)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = {'project_name': "test_project2", 'email': "test@example.com"}
data = {
'project_name': "test_project2",
'email': "test@example.com",
'region': 'RegionOne',
}
response = self.client.put(url, data, format='json',
headers=headers)
self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -844,6 +850,8 @@ class AdminAPITests(APITestCase):
@mock.patch(
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
@mock.patch('stacktask.actions.tenant_setup.models.IdentityManager',
FakeManager)
def test_task_list_filter(self):
"""
"""

View File

@ -285,7 +285,10 @@ class TaskViewTests(APITestCase):
response = self.client.post(url, {'approved': True}, format='json',
headers=headers)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'errors': ['actions invalid']})
self.assertEqual(
response.data,
{'errors': ['Cannot approve an invalid task. ' +
'Update data and rerun pre_approve.']})
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
FakeManager)
@ -522,7 +525,7 @@ class TaskViewTests(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'notes': ['created token']})
response = self.client.post(url, data, format='json', headers=headers)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
data = {'email': "test2@example.com", 'roles': ["_member_"],
'project_id': 'test_project_id'}
@ -530,7 +533,7 @@ class TaskViewTests(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'notes': ['created token']})
response = self.client.post(url, data, format='json', headers=headers)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
FakeManager)

View File

@ -215,8 +215,6 @@ class TaskDetail(APIViewWithLogger):
"""
Allows the updating of action data and retriggering
of the pre_approve step.
Will undo task approval, and clear tokens for the task.
"""
try:
task = Task.objects.get(uuid=uuid)
@ -321,73 +319,121 @@ class TaskDetail(APIViewWithLogger):
{'errors': ['No task with this id.']},
status=404)
if request.data.get('approved', False) is True:
if request.data.get('approved') is not True:
return Response(
{'approved': ["this is a required boolean field."]},
status=400)
if task.completed:
return Response(
{'errors':
['This task has already been completed.']},
status=400)
if task.completed:
return Response(
{'errors':
['This task has already been completed.']},
status=400)
if task.cancelled:
return Response(
{'errors':
['This task has been cancelled.']},
status=400)
if task.cancelled:
return Response(
{'errors':
['This task has been cancelled.']},
status=400)
need_token = False
valid = True
# we check that the task is valid before approving it:
valid = True
for action in task.actions:
if not action.valid:
valid = False
actions = []
if not valid:
return Response(
{'errors':
['Cannot approve an invalid task. ' +
'Update data and rerun pre_approve.']},
status=400)
for action in task.actions:
act_model = action.get_action()
actions.append(act_model)
# We approve the task before running actions,
# that way if something goes wrong we know if it was approved,
# when it was approved, and who approved it last. Subsequent
# reapproval attempts overwrite previous approved_by/on.
task.approved = True
task.approved_by = request.keystone_user
task.approved_on = timezone.now()
task.save()
need_token = False
valid = True
actions = []
for action in task.actions:
act_model = action.get_action()
actions.append(act_model)
try:
act_model.post_approve()
except Exception as e:
notes = {
'errors':
[("Error: '%s' while approving task. " +
"See task itself for details.") % e],
'task': task.uuid
}
create_notification(task, notes)
import traceback
trace = traceback.format_exc()
self.logger.critical(("(%s) - Exception escaped! %s\n" +
"Trace: \n%s") %
(timezone.now(), e, trace))
return Response(notes, status=500)
if not action.valid:
valid = False
if action.need_token:
need_token = True
if valid:
if need_token:
token = create_token(task)
try:
act_model.post_approve()
except Exception as e:
class_conf = settings.TASK_SETTINGS.get(
task.task_type, settings.DEFAULT_TASK_SETTINGS)
# will throw a key error if the token template has not
# been specified
email_conf = class_conf['emails']['token']
send_email(task, email_conf, token)
return Response({'notes': ['created token']},
status=200)
except KeyError as e:
notes = {
'errors':
[("Error: '%s' while approving task. " +
"See task itself for details.") % e],
[("Error: '%s' while sending " +
"token. See task " +
"itself for details.") % e],
'task': task.uuid
}
create_notification(task, notes)
import traceback
trace = traceback.format_exc()
self.logger.critical(("(%s) - Exception escaped! %s\n" +
"Trace: \n%s") %
self.logger.critical(("(%s) - Exception escaped!" +
" %s\n Trace: \n%s") %
(timezone.now(), e, trace))
return Response(notes, status=500)
if not action.valid:
valid = False
if action.need_token:
need_token = True
if valid:
task.approved = True
task.approved_on = timezone.now()
task.save()
if need_token:
token = create_token(task)
response_dict = {
'errors':
["Error: Something went wrong on the " +
"server. It will be looked into shortly."]
}
return Response(response_dict, status=500)
else:
for action in actions:
try:
class_conf = settings.TASK_SETTINGS.get(
task.task_type, settings.DEFAULT_TASK_SETTINGS)
# will throw a key error if the token template has not
# been specified
email_conf = class_conf['emails']['token']
send_email(task, email_conf, token)
return Response({'notes': ['created token']},
status=200)
except KeyError as e:
action.submit({})
except Exception as e:
notes = {
'errors':
[("Error: '%s' while sending " +
"token. See task " +
[("Error: '%s' while submitting " +
"task. See task " +
"itself for details.") % e],
'task': task.uuid
}
@ -399,52 +445,23 @@ class TaskDetail(APIViewWithLogger):
" %s\n Trace: \n%s") %
(timezone.now(), e, trace))
response_dict = {
'errors':
["Error: Something went wrong on the " +
"server. It will be looked into shortly."]
}
return Response(response_dict, status=500)
else:
for action in actions:
try:
action.submit({})
except Exception as e:
notes = {
'errors':
[("Error: '%s' while submitting " +
"task. See task " +
"itself for details.") % e],
'task': task.uuid
}
create_notification(task, notes)
return Response(notes, status=500)
import traceback
trace = traceback.format_exc()
self.logger.critical(("(%s) - Exception escaped!" +
" %s\n Trace: \n%s") %
(timezone.now(), e, trace))
task.completed = True
task.completed_on = timezone.now()
task.save()
return Response(notes, status=500)
# Sending confirmation email:
class_conf = settings.TASK_SETTINGS.get(
task.task_type, settings.DEFAULT_TASK_SETTINGS)
email_conf = class_conf.get(
'emails', {}).get('completed', None)
send_email(task, email_conf)
task.completed = True
task.completed_on = timezone.now()
task.save()
# Sending confirmation email:
class_conf = settings.TASK_SETTINGS.get(
task.task_type, settings.DEFAULT_TASK_SETTINGS)
email_conf = class_conf.get(
'emails', {}).get('completed', None)
send_email(task, email_conf)
return Response(
{'notes': "Task completed successfully."},
status=200)
return Response({'errors': ['actions invalid']}, status=400)
else:
return Response({'approved': ["this field is required."]},
status=400)
return Response(
{'notes': "Task completed successfully."},
status=200)
return Response({'errors': ['actions invalid']}, status=400)
@utils.mod_or_admin
def delete(self, request, uuid, format=None):

View File

@ -151,8 +151,6 @@ USERNAME_IS_EMAIL = CONFIG['USERNAME_IS_EMAIL']
# Keystone admin credentials:
KEYSTONE = CONFIG['KEYSTONE']
DEFAULT_REGION = CONFIG['DEFAULT_REGION']
TOKEN_SUBMISSION_URL = CONFIG['TOKEN_SUBMISSION_URL']
TOKEN_EXPIRE_TIME = CONFIG['TOKEN_EXPIRE_TIME']

View File

@ -63,11 +63,9 @@ KEYSTONE = {
'username': 'admin',
'password': 'openstack',
'project_name': 'admin',
'auth_url': "http://localhost:5000/v3"
'auth_url': "http://localhost:5000/v3",
}
DEFAULT_REGION = 'RegionOne'
TOKEN_SUBMISSION_URL = 'http://localhost:8080/token/'
TOKEN_EXPIRE_TIME = 24
@ -120,10 +118,12 @@ TASK_SETTINGS = {
}
},
'create_project': {
'actions': [
'AddAdminToProject',
'DefaultProjectResources'
]
'additional_actions': [
'AddDefaultUsersToProject',
'NewProjectDefaultNetwork'
],
'default_region': 'RegionOne',
'default_parent_id': None,
},
'reset_password': {
'handle_duplicates': 'cancel',
@ -154,13 +154,18 @@ TASK_SETTINGS = {
}
ACTION_SETTINGS = {
'NewProject': {
'default_roles': {
"project_admin", "project_mod", "_member_", "heat_stack_owner"
},
},
'NewUser': {
'allowed_roles': ['project_mod', 'project_admin', "_member_"]
},
'ResetUser': {
'blacklisted_roles': ['admin']
},
'DefaultProjectResources': {
'NewDefaultNetwork': {
'RegionOne': {
'DNS_NAMESERVERS': ['193.168.1.2', '193.168.1.3'],
'SUBNET_CIDR': '192.168.1.0/24',
@ -169,7 +174,16 @@ ACTION_SETTINGS = {
'router_name': 'somerouter',
'subnet_name': 'somesubnet'
}
},
'AddDefaultUsersToProject': {
'default_users': [
'admin',
],
'default_roles': [
'admin',
],
}
}
ROLES_MAPPING = {
@ -195,7 +209,6 @@ conf_dict = {
"EMAIL_SETTINGS": EMAIL_SETTINGS,
"USERNAME_IS_EMAIL": USERNAME_IS_EMAIL,
"KEYSTONE": KEYSTONE,
"DEFAULT_REGION": DEFAULT_REGION,
"ACTIVE_TASKVIEWS": ACTIVE_TASKVIEWS,
"DEFAULT_TASK_SETTINGS": DEFAULT_TASK_SETTINGS,
"TASK_SETTINGS": TASK_SETTINGS,

View File

@ -40,8 +40,8 @@ conf = {
'username': settings.KEYSTONE['username'],
'password': settings.KEYSTONE['password'],
'project_name': settings.KEYSTONE['project_name'],
"project_domain_name": settings.KEYSTONE.get('domain_name', "default"),
"user_domain_name": settings.KEYSTONE.get('domain_name', "default"),
"project_domain_id": settings.KEYSTONE.get('domain_id', "default"),
"user_domain_id": settings.KEYSTONE.get('domain_id', "default"),
"auth_url": settings.KEYSTONE['auth_url'],
'delay_auth_decision': True,
'include_service_catalog': False,