Adds Variables support to Projects resource

Includes Variables support, which is already present
in several other Craton resources, in the Projects
resource as well.

Project variables will serve as a top-most scope
of variables for resources that belong to a specific
project.

Partial-Bug: 1648626
Closes-Bug: 1661670

Change-Id: I574e834ce409efd8ceafb15fe30a86ae93df2a80
This commit is contained in:
Ian Cordasco 2017-02-15 08:41:49 -06:00 committed by Thomas Maddox
parent 656535624e
commit 603cef7a31
16 changed files with 510 additions and 119 deletions

View File

@ -38,6 +38,7 @@ class Projects(base.Resource):
@base.http_codes
def post(self, context, request_data):
"""Create a new project. Requires super admin privileges."""
LOG.debug("TEM calling dbapi.projects_create")
project_obj = dbapi.projects_create(context, request_data)
location = v1.api.url_for(
@ -45,7 +46,13 @@ class Projects(base.Resource):
)
headers = {'Location': location}
return jsonutils.to_primitive(project_obj), 201, headers
project = jsonutils.to_primitive(project_obj)
if 'variables' in request_data:
project["variables"] = \
jsonutils.to_primitive(project_obj.variables)
else:
project["variables"] = {}
return project, 201, headers
class ProjectById(base.Resource):
@ -54,7 +61,9 @@ class ProjectById(base.Resource):
def get(self, context, id):
"""Get a project details by id. Requires super admin privileges."""
project_obj = dbapi.projects_get_by_id(context, id)
return jsonutils.to_primitive(project_obj), 200, None
project = jsonutils.to_primitive(project_obj)
project['variables'] = jsonutils.to_primitive(project_obj.variables)
return project, 200, None
@base.http_codes
def delete(self, context, id):

View File

@ -1,10 +1,17 @@
from oslo_serialization import jsonutils
from oslo_log import log
from craton.api.v1 import base
from craton.api.v1.resources import utils
from craton import db as dbapi
# NOTE(thomasem): LOG must exist for craton.api.v1.base module to introspect
# and execute this modules LOG.
LOG = log.getLogger(__name__)
class Variables(base.Resource):
@base.http_codes

View File

@ -11,7 +11,7 @@ from craton.api.v1.resources.inventory import networks
VARS_RESOLVE = ", ".join(map(repr, ("hosts", )))
VARS_NOT_RESOLVE = ", ".join(
map(repr, ("network-devices", "cells", "regions", "networks"))
map(repr, ("network-devices", "cells", "regions", "networks", "projects"))
)
routes = [

View File

@ -364,6 +364,7 @@ DefinitionProject = {
"name": {
"type": "string",
},
"variables": DefinitionVariablesSource,
},
}
@ -997,6 +998,10 @@ validators = {
"type": "integer",
"description": "Last project ID of the previous page",
},
"vars": {
"type": "string",
"description": "variable filters to get a project",
},
},
},
},

View File

@ -1,6 +1,7 @@
"""SQLAlchemy backend implementation."""
import sys
import uuid
from oslo_config import cfg
from oslo_db import exception as db_exc
@ -8,7 +9,6 @@ from oslo_db import options as db_options
from oslo_db.sqlalchemy import session
from oslo_db.sqlalchemy import utils as db_utils
from oslo_log import log
from oslo_utils import uuidutils
import sqlalchemy.orm.exc as sa_exc
from sqlalchemy.orm import with_polymorphic
@ -140,6 +140,7 @@ def _get_resource_model(resource):
),
"networks": models.Network,
"regions": models.Region,
"projects": models.Project,
}
return resource_models[resource]
@ -476,29 +477,31 @@ def hosts_labels_delete(context, host_id, labels):
return _device_labels_delete(context, 'hosts', host_id, labels)
@require_admin_context
def projects_get_all(context, filters, pagination_params):
"""Get all the projects."""
session = get_session()
query = model_query(context, models.Project, session=session)
if "vars" in filters:
query = add_var_filters_to_query(query, filters)
return _paginate(context, query, models.Project, session, filters,
pagination_params)
@require_admin_context
def projects_get_by_name(context, project_name):
def projects_get_by_name(context, project_name, filters, pagination_params):
"""Get all projects that match the given name."""
query = model_query(context, models.Project)
query = query.filter(models.Project.name.like(project_name))
if "vars" in filters:
query = add_var_filters_to_query(query, filters)
try:
return query.all()
return _paginate(context, query, models.Project, session, filters,
pagination_params)
except sa_exc.NoResultFound:
raise exceptions.NotFound()
except Exception as err:
raise exceptions.UnknownException(message=err)
@require_admin_context
def projects_get_by_id(context, project_id):
"""Get project by its id."""
query = model_query(context, models.Project)
@ -514,10 +517,11 @@ def projects_get_by_id(context, project_id):
@require_admin_context
def projects_create(context, values):
"""Create a new project with given values."""
LOG.debug("TEM: uuid = {}".format(type(uuid)))
session = get_session()
project = models.Project()
if not values.get('id'):
values['id'] = uuidutils.generate_uuid()
values['id'] = uuid.uuid4()
with session.begin():
project.update(values)
project.save(session)

View File

@ -227,7 +227,7 @@ class VariableMixin(object):
return blamed
class Project(Base):
class Project(Base, VariableMixin):
"""Supports multitenancy for all other schema elements."""
__tablename__ = 'projects'
id = Column(UUIDType(binary=False), primary_key=True)
@ -359,12 +359,12 @@ class Device(Base, VariableMixin):
@property
def resolution_order(self):
# TODO(jimbaker) add self.project to resolution_order
return list(itertools.chain(
[self],
self.ancestors,
[self.cell] if self.cell else [],
[self.region]))
[self.region],
[self.project]))
__mapper_args__ = {
'polymorphic_on': type,

View File

@ -26,4 +26,5 @@ class TestCase(testtools.TestCase):
self.context = make_context(auth_token='fake-token',
user='fake-user',
tenant='fake-tenant',
is_admin=True)
is_admin=True,
is_admin_project=True)

View File

@ -12,6 +12,13 @@ FAKE_DATA_GEN_USERNAME = 'demo'
FAKE_DATA_GEN_TOKEN = 'demo'
FAKE_DATA_GEN_PROJECT_ID = 'b9f10eca66ac4c279c139d01e65f96b4'
FAKE_DATA_GEN_BOOTSTRAP_USERNAME = 'bootstrap'
FAKE_DATA_GEN_BOOTSTRAP_TOKEN = 'bootstrap'
HEADER_TOKEN = 'X-Auth-Token'
HEADER_USERNAME = 'X-Auth-User'
HEADER_PROJECT = 'X-Auth-Project'
class DockerSetup(threading.Thread):
@ -148,17 +155,27 @@ def setup_database(container_ip):
# isolate all test from any external scripts.
projects = meta.tables['projects']
users = meta.tables['users']
variable_assn = meta.tables['variable_association']
with contextlib.closing(engine.connect()) as conn:
transaction = conn.begin()
result = conn.execute(variable_assn.insert(),
discriminator='project')
conn.execute(projects.insert(),
name=FAKE_DATA_GEN_USERNAME,
id=FAKE_DATA_GEN_PROJECT_ID)
id=FAKE_DATA_GEN_PROJECT_ID,
variable_association_id=result.inserted_primary_key[0])
conn.execute(users.insert(),
project_id=FAKE_DATA_GEN_PROJECT_ID,
username=FAKE_DATA_GEN_USERNAME,
api_key=FAKE_DATA_GEN_TOKEN,
is_admin=True)
conn.execute(users.insert(),
project_id=FAKE_DATA_GEN_PROJECT_ID,
username=FAKE_DATA_GEN_BOOTSTRAP_USERNAME,
api_key=FAKE_DATA_GEN_BOOTSTRAP_TOKEN,
is_admin=True,
is_root=True)
transaction.commit()
@ -173,9 +190,9 @@ class TestCase(testtools.TestCase):
data = _container.container_data
self.service_ip = data['NetworkSettings']['IPAddress']
self.url = 'http://{}:8080'.format(self.service_ip)
self.session.headers['X-Auth-Project'] = FAKE_DATA_GEN_PROJECT_ID
self.session.headers['X-Auth-Token'] = FAKE_DATA_GEN_TOKEN
self.session.headers['X-Auth-User'] = FAKE_DATA_GEN_USERNAME
self.session.headers[HEADER_PROJECT] = FAKE_DATA_GEN_PROJECT_ID
self.session.headers[HEADER_USERNAME] = FAKE_DATA_GEN_USERNAME
self.session.headers[HEADER_TOKEN] = FAKE_DATA_GEN_TOKEN
setup_database(self.service_ip)

View File

@ -45,7 +45,7 @@ class APIV1CellTest(APIV1ResourceWithVariablesTestCase):
self.assertEqual('cell-a', cell['name'])
self.assertEqual(variables, cell['variables'])
def test_create_region_supports_vars_ops(self):
def test_create_cell_supports_vars_ops(self):
cell = self.create_cell('new-cell', {'a': 'b'})
self.assert_vars_get_expected(cell['id'], {'a': 'b'})
self.assert_vars_can_be_set(cell['id'])

View File

@ -0,0 +1,139 @@
import copy
from craton.tests import functional
from craton.tests.functional.test_variable_calls import \
APIV1ResourceWithVariablesTestCase
class ProjectTests(functional.TestCase):
def setUp(self):
super(ProjectTests, self).setUp()
self.root_headers = copy.deepcopy(self.session.headers)
self.root_headers[functional.HEADER_USERNAME] = \
functional.FAKE_DATA_GEN_BOOTSTRAP_USERNAME
self.root_headers[functional.HEADER_TOKEN] = \
functional.FAKE_DATA_GEN_BOOTSTRAP_TOKEN
def tearDown(self):
super(ProjectTests, self).tearDown()
def create_project(self, name, variables=None):
url = self.url + '/v1/projects'
payload = {'name': name}
if variables:
payload['variables'] = variables
project = self.post(url, headers=self.root_headers, data=payload)
self.assertEqual(201, project.status_code)
self.assertIn('Location', project.headers)
self.assertEqual(
project.headers['Location'],
"{}/{}".format(url, project.json()['id'])
)
return project.json()
class TestPaginationOfProjects(ProjectTests):
def setUp(self):
super(TestPaginationOfProjects, self).setUp()
self.projects = [
self.create_project('project-{}'.format(i))
for i in range(0, 61)
]
def test_lists_first_thirty_projects(self):
response = self.get(self.url + '/v1/projects')
self.assertSuccessOk(response)
json = response.json()
self.assertIn('projects', json)
projects = json['projects']
self.assertEqual(30, len(projects))
class APIV1ProjectTest(ProjectTests, APIV1ResourceWithVariablesTestCase):
resource = 'projects'
def test_project_create_with_variables(self):
variables = {'a': 'b'}
project_name = 'test'
project = self.create_project(project_name, variables=variables)
self.assertEqual(project_name, project['name'])
self.assertEqual(variables, project['variables'])
def test_create_project_supports_vars_ops(self):
project = self.create_project('test', {'a': 'b'})
self.assert_vars_get_expected(project['id'], {'a': 'b'})
self.assert_vars_can_be_set(project['id'])
self.assert_vars_can_be_deleted(project['id'])
def test_project_create_with_duplicate_name_works(self):
project_name = 'test'
self.create_project(project_name)
url = self.url + '/v1/projects'
payload = {'name': project_name}
project = self.post(url, headers=self.root_headers, data=payload)
self.assertEqual(201, project.status_code)
def test_project_get_all_with_name_filter(self):
proj1 = 'test1'
proj2 = 'test2'
self.create_project(proj2)
for i in range(3):
self.create_project(proj1)
url = self.url + '/v1/projects?name={}'.format(proj1)
resp = self.get(url, headers=self.root_headers)
projects = resp.json()['projects']
self.assertEqual(3, len(projects))
for project in projects:
self.assertEqual(proj1, project['name'])
def test_get_project_details(self):
project_name = 'test'
project_vars = {"who": "that"}
project = self.create_project(project_name, variables=project_vars)
url = self.url + '/v1/projects/{}'.format(project['id'])
project_with_detail = self.get(url, headers=self.root_headers)
self.assertEqual(project_name, project_with_detail.json()['name'])
self.assertEqual(project_vars, project_with_detail.json()['variables'])
def test_project_delete(self):
project1 = self.create_project('test1')
url = self.url + '/v1/projects'
projects = self.get(url, headers=self.root_headers)
# NOTE(thomasem): Have to include the default project created by
# test setup.
self.assertEqual(2, len(projects.json()['projects']))
delurl = self.url + '/v1/projects/{}'.format(project1['id'])
self.delete(delurl, headers=self.root_headers)
projects = self.get(url, headers=self.root_headers)
self.assertEqual(1, len(projects.json()['projects']))
def test_project_variables_update(self):
project_name = 'test'
project = self.create_project(project_name)
variables = {"bumbleywump": "cucumberpatch"}
put_url = self.url + '/v1/projects/{}/variables'.format(project['id'])
resp = self.put(put_url, headers=self.root_headers, data=variables)
self.assertEqual(200, resp.status_code)
get_url = self.url + '/v1/projects/{}'.format(project['id'])
project = self.get(get_url, headers=self.root_headers)
self.assertEqual(variables, project.json()['variables'])
def test_project_variables_delete(self):
project_name = 'test'
delete_key = 'bumbleywump'
variables = {
delete_key: 'cucumberpatch'
}
expected_vars = {'foo': 'bar'}
variables.update(expected_vars)
project = self.create_project(project_name, variables=variables)
self.assert_vars_get_expected(project['id'], variables)
self.assert_vars_can_be_deleted(project['id'])

View File

@ -11,30 +11,37 @@ default_pagination = {'limit': 30, 'marker': None}
class HostsDBTestCase(base.DBTestCase):
project_id = uuid.uuid4().hex
mock_project_id = uuid.uuid4().hex
def make_region(self, name, **variables):
def make_project(self, name, **variables):
project = dbapi.projects_create(
self.context,
{"name": name,
"variables": variables})
return str(project.id)
def make_region(self, project_id, name, **variables):
region = dbapi.regions_create(
self.context,
{'name': name,
'project_id': self.project_id,
'project_id': project_id,
'variables': variables})
return region.id
def make_cell(self, region_id, name, **variables):
def make_cell(self, project_id, region_id, name, **variables):
cell = dbapi.cells_create(
self.context,
{'name': name,
'project_id': self.project_id,
'project_id': project_id,
'region_id': region_id,
'variables': variables})
return cell.id
def make_host(self, region_id, name, ip_address, host_type, cell_id=None,
parent_id=None, labels=None, **variables):
def make_host(self, project_id, region_id, name, ip_address, host_type,
cell_id=None, parent_id=None, labels=None, **variables):
if cell_id:
host = {'name': name,
'project_id': self.project_id,
'project_id': project_id,
'region_id': region_id,
'cell_id': cell_id,
'ip_address': ip_address,
@ -45,7 +52,7 @@ class HostsDBTestCase(base.DBTestCase):
'variables': variables}
else:
host = {'name': name,
'project_id': self.project_id,
'project_id': project_id,
'region_id': region_id,
'ip_address': ip_address,
'parent_id': parent_id,
@ -58,24 +65,26 @@ class HostsDBTestCase(base.DBTestCase):
return host.id
def make_very_small_cloud(self, with_cell=False):
project_id = self.make_project('project_1', foo='P1', zoo='P2')
region_id = self.make_region(
project_id,
'region_1',
foo='R1', bar='R2', bax='R3')
if with_cell:
cell_id = self.make_cell(region_id, 'cell_1', bar='C2')
cell_id = self.make_cell(project_id, region_id, 'cell_1', bar='C2')
else:
cell_id = None
host_id = self.make_host(region_id, 'www1.example.com',
host_id = self.make_host(project_id, region_id, 'www1.example.com',
IPAddress(u'10.1.2.101'), 'server',
cell_id=cell_id, foo='H1', baz='H3')
return region_id, cell_id, host_id
return project_id, region_id, cell_id, host_id
def test_hosts_create(self):
# Need to do this query despite creation above because other
# elements (cell, region) were in separate committed sessions
# when the host was created. Verify these linked elements load
# correctly
region_id, cell_id, host_id = self.make_very_small_cloud(
project_id, region_id, cell_id, host_id = self.make_very_small_cloud(
with_cell=True)
host = dbapi.hosts_get_by_id(self.context, host_id)
self.assertEqual(host.region.id, region_id)
@ -86,36 +95,40 @@ class HostsDBTestCase(base.DBTestCase):
# Verify resolved variables/blames override properly
self.assertEqual(
[obj.id for obj in host.resolution_order],
[host_id, cell_id, region_id])
[host_id, cell_id, region_id, uuid.UUID(project_id)])
self.assertEqual(
[variables for variables in host.resolution_order_variables],
[{'foo': 'H1', 'baz': 'H3'},
{'bar': 'C2'},
{'foo': 'R1', 'bar': 'R2', 'bax': 'R3'}])
{'foo': 'R1', 'bar': 'R2', 'bax': 'R3'},
{'foo': 'P1', 'zoo': 'P2'}])
self.assertEqual(
host.resolved,
{'foo': 'H1', 'bar': 'C2', 'baz': 'H3', 'bax': 'R3'})
{'foo': 'H1', 'bar': 'C2', 'baz': 'H3', 'bax': 'R3', 'zoo': 'P2'})
blame = host.blame(['foo', 'bar'])
blame = host.blame(['foo', 'bar', 'zoo'])
self.assertEqual(blame['foo'].source.name, 'www1.example.com')
self.assertEqual(blame['foo'].variable.value, 'H1')
self.assertEqual(blame['bar'].source.name, 'cell_1')
self.assertEqual(blame['bar'].variable.value, 'C2')
self.assertEqual(blame['zoo'].source.name, 'project_1')
self.assertEqual(blame['zoo'].variable.value, 'P2')
def test_hosts_create_duplicate_raises(self):
region_id = self.make_region('region_1')
self.make_host(region_id, 'www1.example.com',
region_id = self.make_region(self.mock_project_id, 'region_1')
self.make_host(self.mock_project_id, region_id, 'www1.example.com',
IPAddress(u'10.1.2.101'), 'server')
new_host = {'name': 'www1.example.com', 'region_id': region_id,
'ip_address': IPAddress(u'10.1.2.101'),
'device_type': 'server', 'project_id': self.project_id}
'device_type': 'server',
'project_id': self.mock_project_id}
self.assertRaises(exceptions.DuplicateDevice, dbapi.hosts_create,
self.context, new_host)
def test_hosts_create_without_cell(self):
region_id, _, host_id = self.make_very_small_cloud()
project_id, region_id, _, host_id = self.make_very_small_cloud()
host = dbapi.hosts_get_by_id(self.context, host_id)
self.assertEqual(host.region.id, region_id)
self.assertEqual(host.region.name, 'region_1')
@ -124,26 +137,29 @@ class HostsDBTestCase(base.DBTestCase):
# Verify resolved variables/blames override properly
self.assertEqual(
[obj.id for obj in host.resolution_order],
[host_id, region_id])
[host_id, region_id, uuid.UUID(project_id)])
self.assertEqual(
[variables for variables in host.resolution_order_variables],
[{'foo': 'H1', 'baz': 'H3'},
{'foo': 'R1', 'bar': 'R2', 'bax': 'R3'}])
{'foo': 'R1', 'bar': 'R2', 'bax': 'R3'},
{'foo': 'P1', 'zoo': 'P2'}])
self.assertEqual(
host.resolved,
{'foo': 'H1', 'bar': 'R2', 'baz': 'H3', 'bax': 'R3'})
{'foo': 'H1', 'bar': 'R2', 'baz': 'H3', 'bax': 'R3', 'zoo': 'P2'})
blame = host.blame(['foo', 'bar'])
blame = host.blame(['foo', 'bar', 'zoo'])
self.assertEqual(blame['foo'].source.name, 'www1.example.com')
self.assertEqual(blame['foo'].variable.value, 'H1')
self.assertEqual(blame['bar'].source.name, 'region_1')
self.assertEqual(blame['bar'].variable.value, 'R2')
self.assertEqual(blame['zoo'].source.name, 'project_1')
self.assertEqual(blame['zoo'].variable.value, 'P2')
def test_hosts_update(self):
region_id = self.make_region('region_1')
host_id = self.make_host(region_id, 'example',
region_id = self.make_region(self.mock_project_id, 'region_1')
host_id = self.make_host(self.mock_project_id, region_id, 'example',
IPAddress(u'10.1.2.101'), 'server',
bar='bar2')
name = "Host_New"
@ -151,14 +167,19 @@ class HostsDBTestCase(base.DBTestCase):
self.assertEqual(res.name, name)
def test_hosts_variable_resolved_with_parent(self):
project_id = self.make_project(
'project_1',
foo='P1', zoo='P2')
region_id = self.make_region(
project_id,
'region_1',
foo='R1', bar='R2', bax='R3')
cell_id = self.make_cell(region_id, 'cell_1', bar='C2')
host1_id = self.make_host(region_id, 'www1.example.com',
cell_id = self.make_cell(project_id, region_id, 'cell_1',
bar='C2')
host1_id = self.make_host(project_id, region_id, 'www1.example.com',
IPAddress(u'10.1.2.101'), 'server',
cell_id=cell_id, foo='H1', baz='H3')
host2_id = self.make_host(region_id, 'www1.example2.com',
host2_id = self.make_host(project_id, region_id, 'www1.example2.com',
IPAddress(u'10.1.2.102'), 'server',
cell_id=cell_id, parent_id=host1_id)
host2 = dbapi.hosts_get_by_id(self.context, host2_id)
@ -166,28 +187,32 @@ class HostsDBTestCase(base.DBTestCase):
# Verify resolved variables/blames override properly
self.assertEqual(
[obj.id for obj in host2.resolution_order],
[host2_id, host1_id, cell_id, region_id])
[host2_id, host1_id, cell_id, region_id, uuid.UUID(project_id)])
self.assertEqual(
[variables for variables in host2.resolution_order_variables],
[{},
{'baz': 'H3', 'foo': 'H1'},
{'bar': 'C2'},
{'bar': 'R2', 'foo': 'R1', 'bax': 'R3'}])
{'bar': 'R2', 'foo': 'R1', 'bax': 'R3'},
{'foo': 'P1', 'zoo': 'P2'}])
self.assertEqual(
host2.resolved,
{'foo': 'H1', 'bar': 'C2', 'baz': 'H3', 'bax': 'R3'})
{'foo': 'H1', 'bar': 'C2', 'baz': 'H3', 'bax': 'R3', 'zoo': 'P2'})
blame = host2.blame(['foo', 'bar'])
blame = host2.blame(['foo', 'bar', 'zoo'])
self.assertEqual(blame['foo'].source.name, 'www1.example.com')
self.assertEqual(blame['foo'].variable.value, 'H1')
self.assertEqual(blame['bar'].source.name, 'cell_1')
self.assertEqual(blame['bar'].variable.value, 'C2')
self.assertEqual(blame['zoo'].source.name, 'project_1')
self.assertEqual(blame['zoo'].variable.value, 'P2')
def test_hosts_variables_no_resolved(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
project_id = self.make_project('project_1', zoo='P2')
region_id = self.make_region(project_id, 'region_1', foo='R1')
host_id = self.make_host(project_id, region_id, 'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server', bar='bar2')
host = dbapi.hosts_get_by_id(self.context, host_id)
@ -195,8 +220,11 @@ class HostsDBTestCase(base.DBTestCase):
self.assertEqual(host.variables, {'bar': 'bar2'})
def test_hosts_resolved_vars_no_cells(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
project_id = self.make_project('project_1')
region_id = self.make_region(project_id, 'region_1',
foo='R1')
host_id = self.make_host(project_id, region_id,
'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server', bar='bar2')
host = dbapi.hosts_get_by_id(self.context, host_id)
@ -204,16 +232,20 @@ class HostsDBTestCase(base.DBTestCase):
self.assertEqual(host.resolved, {'bar': 'bar2', 'foo': 'R1'})
def test_host_labels_create(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
region_id = self.make_region(self.mock_project_id, 'region_1',
foo='R1')
host_id = self.make_host(self.mock_project_id, region_id,
'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server', bar='bar2')
labels = {"labels": ["tom", "jerry"]}
dbapi.hosts_labels_update(self.context, host_id, labels)
def test_host_labels_delete(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
region_id = self.make_region(self.mock_project_id, 'region_1',
foo='R1')
host_id = self.make_host(self.mock_project_id, region_id,
'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server', bar='bar2')
_labels = {"labels": ["tom", "jerry", "jones"]}
@ -226,12 +258,14 @@ class HostsDBTestCase(base.DBTestCase):
self.assertEqual(host.labels, {"jerry", "jones"})
def test_hosts_get_all_with_filter_cell_id(self):
region_id = self.make_region('region_1', foo='R1')
cell_id1 = self.make_cell(region_id, 'cell_1', bar='C2')
cell_id2 = self.make_cell(region_id, 'cell_2', bar='C2')
project_id = self.make_project('project_1', foo='P1', zoo='P2')
region_id = self.make_region(project_id, 'region_1', foo='R1')
cell_id1 = self.make_cell(project_id, region_id, 'cell_1', bar='C2')
cell_id2 = self.make_cell(project_id, region_id, 'cell_2', bar='C2')
self.assertNotEqual(cell_id1, cell_id2)
self.make_host(
project_id,
region_id,
'www.example.xyz',
IPAddress(u'10.1.2.101'),
@ -239,6 +273,7 @@ class HostsDBTestCase(base.DBTestCase):
cell_id=cell_id1,
)
self.make_host(
project_id,
region_id,
'www.example.abc',
IPAddress(u'10.1.2.102'),
@ -261,8 +296,9 @@ class HostsDBTestCase(base.DBTestCase):
self.assertEqual(res[0].name, 'www.example.xyz')
def test_hosts_get_all_with_filters(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
project_id = self.make_project('project_1', foo='P1', zoo='P2')
region_id = self.make_region(project_id, 'region_1', foo='R1')
host_id = self.make_host(project_id, region_id, 'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server')
variables = {"key1": "value1", "key2": "value2"}
@ -279,8 +315,9 @@ class HostsDBTestCase(base.DBTestCase):
self.assertEqual(res[0].name, 'www.example.xyz')
def test_hosts_get_with_key_value_filters(self):
region_id = self.make_region('region_1', foo='R1')
host1 = self.make_host(region_id, 'www.example.xyz',
project_id = self.make_project('project_1', foo='P1', zoo='P2')
region_id = self.make_region(project_id, 'region_1', foo='R1')
host1 = self.make_host(project_id, region_id, 'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server')
variables = {"key1": "example1", "key2": "Tom"}
@ -288,7 +325,7 @@ class HostsDBTestCase(base.DBTestCase):
self.context, "hosts", host1, variables
)
# Second host with own variables
host2 = self.make_host(region_id, 'www.example2.xyz',
host2 = self.make_host(project_id, region_id, 'www.example2.xyz',
IPAddress(u'10.1.2.102'),
'server')
variables = {"key1": "example2", "key2": "Tom"}
@ -306,8 +343,9 @@ class HostsDBTestCase(base.DBTestCase):
self.assertEqual(len(res), 2)
def test_hosts_get_all_with_filters_noexist(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
project_id = self.make_project('project_1', foo='P1', zoo='P2')
region_id = self.make_region(project_id, 'region_1', foo='R1')
host_id = self.make_host(project_id, region_id, 'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server')
variables = {"key1": "value1", "key2": "value2"}

View File

@ -1,4 +1,5 @@
from oslo_utils import uuidutils
import copy
import uuid
from craton import exceptions
from craton.db import api as dbapi
@ -14,39 +15,73 @@ class ProjectsDBTestCase(base.DBTestCase):
def test_create_project(self):
# Set root, as only admin project can create other projects
self.context.is_admin_project = True
project = dbapi.projects_create(self.context, project1)
self.assertTrue(uuidutils.is_uuid_like(project['id']))
self.assertEqual(project['name'], project1['name'])
def test_create_project_no_root_fails(self):
context = copy.deepcopy(self.context)
context.is_admin_project = False
self.assertRaises(exceptions.AdminRequired,
dbapi.projects_create,
self.context,
context,
project1)
def test_project_get_all(self):
self.context.is_admin_project = True
dbapi.projects_create(self.context, project1)
dbapi.projects_create(self.context, project2)
res = dbapi.projects_get_all(self.context, {}, default_pagination)
res, _ = dbapi.projects_get_all(self.context, {}, default_pagination)
self.assertEqual(len(res), 2)
def test_project_get_no_admin_project_raises(self):
self.context.is_admin_project = True
def test_project_get_by_name(self):
dbapi.projects_create(self.context, project1)
dbapi.projects_create(self.context, project2)
# Now set admin_project = false to become normal project user
self.context.is_admin_project = False
self.assertRaises(exceptions.AdminRequired,
dbapi.projects_get_all,
self.context,
{}, default_pagination)
res, _ = dbapi.projects_get_by_name(self.context, project1['name'], {},
default_pagination)
self.assertEqual(len(res), 1)
self.assertEqual(res[0].name, project1['name'])
def test_project_get_by_id(self):
self.context.is_admin_project = True
project = dbapi.projects_create(self.context, project1)
res = dbapi.projects_get_by_id(self.context, project['id'])
self.assertEqual(str(res['id']), project['id'])
self.assertEqual(str(res['id']), str(project['id']))
def test_project_create_id_uuid_type(self):
project = dbapi.projects_create(self.context, project1)
self.assertEqual(type(project['id']), uuid.UUID)
def test_project_get_id_uuid_type(self):
project = dbapi.projects_create(self.context, project1)
res = dbapi.projects_get_by_id(self.context, project['id'])
self.assertEqual(type(res['id']), uuid.UUID)
def test_project_variables_update_does_update_variables(self):
create_res = dbapi.projects_create(self.context, project1)
res = dbapi.projects_get_by_id(self.context, create_res.id)
self.assertEqual(res.variables, {})
variables = {"key1": "value1", "key2": "value2"}
res = dbapi.variables_update_by_resource_id(
self.context, "projects", res.id, variables
)
self.assertEqual(res.variables, variables)
new_variables = {"key1": "tom", "key2": "cat"}
res = dbapi.variables_update_by_resource_id(
self.context, "projects", res.id, new_variables
)
self.assertEqual(res.variables, new_variables)
def test_project_variables_delete(self):
create_res = dbapi.projects_create(self.context, project1)
res = dbapi.projects_get_by_id(self.context, create_res.id)
self.assertEqual(res.variables, {})
variables = {"key1": "value1", "key2": "value2"}
res = dbapi.variables_update_by_resource_id(
self.context, "projects", res.id, variables
)
self.assertEqual(res.variables, variables)
# NOTE(sulo): we delete variables by their key
res = dbapi.variables_delete_by_resource_id(
self.context, "projects", res.id, {"key1": "key1"}
)
self.assertEqual(res.variables, {"key2": "value2"})

View File

@ -6,25 +6,40 @@ from craton.tests.unit.db import base
class VariablesDBTestCase:
project_id = "5a4e32e1-8571-4c2c-a088-a11f98900355"
def create_region(self, name, variables=None):
def _get_mock_resource_id(self):
# NOTE(thomasem): Project IDs are UUIDs not integers
if self.resources_type in ("projects",):
return "5a4e32e1-8571-4c2c-a088-a11f98900355"
return 1
def create_project(self, name, variables=None):
project = dbapi.projects_create(
self.context,
{
"name": name,
"variables": variables or {},
},
)
return project.id
def create_region(self, name, project_id, variables=None):
region = dbapi.regions_create(
self.context,
{
'name': name,
'project_id': self.project_id,
'project_id': project_id,
'variables': variables or {},
},
)
return region.id
def create_cell(self, name, region_id, variables=None):
def create_cell(self, name, project_id, region_id, variables=None):
cell = dbapi.cells_create(
self.context,
{
'name': name,
'project_id': self.project_id,
'project_id': project_id,
'region_id': region_id,
'variables': variables or {}
},
@ -32,12 +47,12 @@ class VariablesDBTestCase:
return cell.id
def create_host(
self, name, region_id, ip_address, host_type, cell_id=None,
parent_id=None, labels=None, variables=None,
self, name, project_id, region_id, ip_address, host_type,
cell_id=None, parent_id=None, labels=None, variables=None,
):
host = {
'name': name,
'project_id': self.project_id,
'project_id': project_id,
'region_id': region_id,
'cell_id': cell_id,
'ip_address': ip_address,
@ -54,12 +69,12 @@ class VariablesDBTestCase:
return host.id
def create_network(
self, name, region_id, cidr, gateway, netmask, cell_id=None,
variables=None,
self, name, project_id, region_id, cidr, gateway, netmask,
cell_id=None, variables=None,
):
network = {
'name': name,
'project_id': self.project_id,
'project_id': project_id,
'region_id': region_id,
'cell_id': cell_id,
'cidr': cidr,
@ -74,12 +89,12 @@ class VariablesDBTestCase:
return network.id
def create_network_device(
self, name, region_id, ip_address, network_device_type,
self, name, project_id, region_id, ip_address, network_device_type,
cell_id=None, parent_id=None, labels=None, variables=None,
):
network_device = {
'name': name,
'project_id': self.project_id,
'project_id': project_id,
'region_id': region_id,
'cell_id': cell_id,
'ip_address': ip_address,
@ -98,10 +113,13 @@ class VariablesDBTestCase:
return network_device.id
def setup_host(self, variables):
region_id = self.create_region(name='region1')
cell_id = self.create_cell(name="cell1", region_id=region_id)
project_id = self.create_project(name='project1')
region_id = self.create_region(name='region1', project_id=project_id)
cell_id = self.create_cell(name="cell1", project_id=project_id,
region_id=region_id)
host_id = self.create_host(
name="host1",
project_id=project_id,
region_id=region_id,
ip_address="192.168.2.1",
host_type="server",
@ -114,10 +132,13 @@ class VariablesDBTestCase:
return host_id
def setup_network_device(self, variables):
region_id = self.create_region(name='region1')
cell_id = self.create_cell(name="cell1", region_id=region_id)
project_id = self.create_project(name='project1')
region_id = self.create_region(name='region1', project_id=project_id)
cell_id = self.create_cell(name="cell1", project_id=project_id,
region_id=region_id)
network_device_id = self.create_network_device(
name="network_device1",
project_id=project_id,
region_id=region_id,
ip_address="192.168.2.1",
network_device_type="switch",
@ -130,10 +151,13 @@ class VariablesDBTestCase:
return network_device_id
def setup_network(self, variables):
region_id = self.create_region(name='region1')
cell_id = self.create_cell(name="cell1", region_id=region_id)
project_id = self.create_project(name='project1')
region_id = self.create_region(name='region1', project_id=project_id)
cell_id = self.create_cell(name="cell1", project_id=project_id,
region_id=region_id)
network_id = self.create_network(
name="network1",
project_id=project_id,
region_id=region_id,
cell_id=cell_id,
cidr="192.168.2.0/24",
@ -145,9 +169,11 @@ class VariablesDBTestCase:
return network_id
def setup_cell(self, variables):
region_id = self.create_region(name='region1')
project_id = self.create_project(name='project1')
region_id = self.create_region(name='region1', project_id=project_id)
cell_id = self.create_cell(
name="cell1",
project_id=project_id,
region_id=region_id,
variables=variables,
)
@ -155,13 +181,19 @@ class VariablesDBTestCase:
return cell_id
def setup_region(self, variables):
project_id = self.create_project(name='project1')
region_id = self.create_region(
name='region1',
project_id=project_id,
variables=variables,
)
return region_id
def setup_project(self, variables):
project_id = self.create_project(name='project1', variables=variables)
return project_id
def setup_resource(self, *args, **kwargs):
setup_fn = {
"cells": self.setup_cell,
@ -169,6 +201,7 @@ class VariablesDBTestCase:
"networks": self.setup_network,
"network-devices": self.setup_network_device,
"regions": self.setup_region,
"projects": self.setup_project,
}
return setup_fn[self.resources_type](*args, *kwargs)
@ -196,7 +229,7 @@ class VariablesDBTestCase:
dbapi.resource_get_by_id,
context=self.context,
resources=self.resources_type,
resource_id=1,
resource_id=self._get_mock_resource_id(),
)
def test_variables_update_by_resource_id_existing_empty(self):
@ -225,13 +258,12 @@ class VariablesDBTestCase:
self.assertEqual(variables, validate.variables)
def test_variables_update_by_resource_id_not_found(self):
self.assertRaises(
exceptions.NotFound,
dbapi.variables_update_by_resource_id,
context=self.context,
resources=self.resources_type,
resource_id=1,
resource_id=self._get_mock_resource_id(),
data={"key1": "value1"},
)
@ -309,7 +341,7 @@ class VariablesDBTestCase:
dbapi.variables_delete_by_resource_id,
context=self.context,
resources=self.resources_type,
resource_id=1,
resource_id=self._get_mock_resource_id(),
data={"key1": "value1"},
)
@ -364,3 +396,7 @@ class RegionsVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
class NetworksVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
resources_type = "networks"
class ProjectsVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
resources_type = "projects"

View File

@ -1,4 +1,5 @@
import copy
import uuid
"""
@ -8,16 +9,19 @@ objects for test.
class Project(object):
def __init__(self, id, name):
self.id = id
def __init__(self, id, name, variables):
self.id = uuid.UUID(id)
self.name = name
self.variables = variables
def items(self):
return iter(self.__dict__.items())
PROJECT1 = Project("4534dcb4-dacd-474f-8afc-8bd5ab2d26e8", "project1")
PROJECT2 = Project("77c527cb-837d-4fcb-bafb-af37ba3d13a4", "project2")
PROJECT1 = Project("4534dcb4-dacd-474f-8afc-8bd5ab2d26e8",
"project1", {"key1": "value1", "key2": "value2"})
PROJECT2 = Project("77c527cb-837d-4fcb-bafb-af37ba3d13a4",
"project2", {"key1": "value1", "key2": "value2"})
class User(object):

View File

@ -817,6 +817,95 @@ class APIV1ProjectsTest(APIV1Test):
resp = self.get('v1/projects')
self.assertEqual(resp.status_code, 401)
@mock.patch.object(dbapi, 'projects_get_by_id')
def test_project_get_by_id(self, mock_project):
proj1 = fake_resources.PROJECT1
proj1_id = str(proj1.id)
mock_project.return_value = proj1
resp = self.get('v1/projects/{}'.format(proj1_id))
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.json['id'], proj1_id)
@mock.patch.object(dbapi, 'projects_get_by_name')
def test_project_get_by_name(self, mock_projects):
proj1 = fake_resources.PROJECT1
proj1_id = str(proj1.id)
return_value = ([proj1], {})
mock_projects.return_value = return_value
resp = self.get('v1/projects?name=project1')
self.assertEqual(resp.status_code, 200)
projects = resp.json['projects']
self.assertEqual(len(projects), 1)
self.assertEqual(projects[0]['id'], proj1_id)
class APIV1ProjectsVariablesTest(APIV1Test):
@mock.patch.object(dbapi, 'resource_get_by_id')
def test_projects_get_variables(self, mock_project):
mock_project.return_value = fake_resources.PROJECT1
resp = self.get(
'v1/projects/{}/variables'.format(fake_resources.PROJECT1.id)
)
expected = {"variables": {"key1": "value1", "key2": "value2"}}
self.assertEqual(resp.json, expected)
@mock.patch.object(dbapi, 'variables_update_by_resource_id')
def test_projects_put_variables(self, mock_project):
proj1 = fake_resources.PROJECT1
proj1_id = str(proj1.id)
db_return_value = copy.deepcopy(proj1)
db_return_value.variables["a"] = "b"
mock_project.return_value = db_return_value
payload = {"a": "b"}
db_data = payload.copy()
resp = self.put(
'v1/projects/{}/variables'.format(proj1_id),
data=payload
)
self.assertEqual(resp.status_code, 200)
mock_project.assert_called_once_with(mock.ANY, "projects", proj1_id,
db_data)
expected = {
"variables": {"key1": "value1", "key2": "value2", "a": "b"},
}
self.assertDictEqual(expected, resp.json)
@mock.patch.object(dbapi, 'variables_update_by_resource_id')
def test_projects_put_variables_bad_data_type(self, mock_project):
payload = ["a", "b"]
resp = self.put(
'v1/projects/{}/variables'.format(fake_resources.PROJECT1.id),
data=payload
)
self.assertEqual(400, resp.status_code)
mock_project.assert_not_called()
@mock.patch.object(dbapi, 'variables_delete_by_resource_id')
def test_projects_delete_variables(self, mock_project):
proj1 = fake_resources.PROJECT1
proj1_id = str(proj1.id)
payload = ["key1"]
db_data = payload.copy()
resp = self.delete(
'v1/projects/{}/variables'.format(proj1_id), data=payload
)
self.assertEqual(resp.status_code, 204)
mock_project.assert_called_once_with(mock.ANY, "projects", proj1_id,
db_data)
@mock.patch.object(dbapi, 'variables_delete_by_resource_id')
def test_projects_delete_variables_bad_data_type(self, mock_project):
payload = {'a': 'b'}
resp = self.delete(
'v1/projects/{}/variables'.format(fake_resources.PROJECT1.id),
data=payload
)
self.assertEqual(400, resp.status_code)
mock_project.assert_not_called()
class APIV1UsersTest(APIV1Test):
@mock.patch.object(dbapi, 'users_create')

View File

@ -23,11 +23,18 @@ USERNAME="demo"
TOKEN="demo"
PROJECT="b9f10eca66ac4c279c139d01e65f96b4"
###################################
# Create initial project and user #
###################################
mysql -u root craton -e "INSERT into projects (created_at, updated_at, name, id) values (NOW(), NOW(), '$USERNAME', '$PROJECT')"
BOOTSTRAP_USERNAME="bootstrap"
BOOTSTRAP_TOKEN="bootstrap"
PROJECT_DISCRIMINATOR='project'
####################################
# Create initial project and users #
####################################
PROJECT_VA_ID=$(mysql -u root craton -e "INSERT into variable_association (created_at, updated_at, discriminator) values (NOW(), NOW(), '$PROJECT_DISCRIMINATOR'); SELECT LAST_INSERT_ID();" | grep -Eo '[0-9]+')
mysql -u root craton -e "INSERT into projects (created_at, updated_at, name, variable_association_id, id) values (NOW(), NOW(), '$USERNAME', $PROJECT_VA_ID, '$PROJECT')"
mysql -u root craton -e "INSERT into users (created_at, updated_at, project_id, username, api_key, is_admin) values (NOW(), NOW(), '$PROJECT', '$USERNAME', '$TOKEN', False)"
mysql -u root craton -e "INSERT into users (created_at, updated_at, project_id, username, api_key, is_root, is_admin) values (NOW(), NOW(), '$PROJECT', '$BOOTSTRAP_USERNAME', '$BOOTSTRAP_TOKEN', True, True)"
#########################
# Start the API service #