Updated API and tests

Supported get clusters by querying deploy state
Fixed warning message "baseException.message deprecated in Python 2.6"
Add tests for modified API
Fixed one string concatenation bug
Change-Id: Ifec3854c286929b69bd689407b4ac409f520fac6
This commit is contained in:
grace.yu 2014-02-06 10:17:06 -08:00
parent 7bb1a48246
commit 4db268c7c4
4 changed files with 157 additions and 54 deletions

View File

@ -13,6 +13,7 @@ from compass.db.model import SwitchConfig
from compass.db.model import Machine as ModelMachine
from compass.db.model import Cluster as ModelCluster
from compass.db.model import ClusterHost as ModelClusterHost
from compass.db.model import ClusterState
from compass.db.model import Adapter
from compass.db.model import Role
@ -276,8 +277,9 @@ class Switch(Resource):
:param switch_id: the unique identifier of the switch.
"""
err_msg = "The delete API for switch has not been implemented!"
return errors.handle_not_allowed_method(
errors.MethodNotAllowed())
errors.MethodNotAllowed(err_msg))
class MachineList(Resource):
@ -496,7 +498,7 @@ class Cluster(Resource):
adapter = session.query(Adapter).filter_by(id=adapter_id).first()
if not adapter:
error_msg = "No adapter id=%s can be found!"
error_msg = "No adapter id=%s can be found!" % adapter_id
return errors.handle_not_exist(
errors.ObjectDoesNotExist(error_msg))
@ -574,9 +576,39 @@ class Cluster(Resource):
def list_clusters():
"""Lists the details of all clusters"""
endpoint = '/clusters'
state = request.args.get('state', None, type=str)
results = []
with database.session() as session:
clusters = session.query(ModelCluster).all()
clusters = []
if not state:
# Get all clusters
clusters = session.query(ModelCluster).all()
elif state == 'undeployed':
clusters_state = session.query(ClusterState.id).all()
cluster_ids = [t[0] for t in clusters_state]
# The cluster has not been deployed yet
clusters = session.query(ModelCluster)\
.filter(~ModelCluster.id.in_(cluster_ids))\
.all()
elif state == 'installing':
# The deployment of this cluster is in progress.
clusters = session.query(ModelCluster)\
.filter(ModelCluster.id == ClusterState.id,
or_(ClusterState.state == 'INSTALLING',
ClusterState.state == 'UNINITIALIZED'))\
.all()
elif state == 'failed':
# The deployment of this cluster is failed.
clusters = session.query(ModelCluster)\
.filter(ModelCluster.id == ClusterState.id,
ClusterState.state == 'ERROR')\
.all()
elif state == 'successful':
clusters = session.query(ModelCluster)\
.filter(ModelCluster.id == ClusterState.id,
ClusterState.state == 'READY')\
.all()
if clusters:
for cluster in clusters:
@ -860,7 +892,7 @@ class ClusterHostConfig(Resource):
if test_host:
error_msg = ("Hostname '%s' has been used for other host "
"in the cluster, cluster ID is %s!"
% hostname, cluster_id)
% (hostname, cluster_id))
return errors.handle_invalid_usage(
errors.UserInvalidUsage(error_msg))
@ -886,7 +918,7 @@ class ClusterHostConfig(Resource):
:param host_id: the unique identifier of the host
:param subkey: the attribute name in configuration
"""
available_delete_keys = ['ip', 'roles']
available_delete_keys = ['roles']
with database.session() as session:
host = session.query(ModelClusterHost).filter_by(id=host_id)\
.first()
@ -907,7 +939,7 @@ class ClusterHostConfig(Resource):
config = json.loads(host.config_data)
# Set the subkey's value to ""
util.update_dict_value(subkey, "", config)
util.update_dict_value(subkey, config)
host.config = config
return util.make_json_response(
@ -934,6 +966,7 @@ class ClusterHost(Resource):
host_res['hostname'] = host.hostname
host_res['mutable'] = host.mutable
host_res['id'] = host.id
host_res['switch_ip'] = host.machine.switch.ip
host_res['link'] = {
"href": '/'.join((self.ENDPOINT, str(host.id))),
"rel": "self"
@ -983,6 +1016,7 @@ def list_clusterhosts():
host_res['hostname'] = host.hostname
host_res['mutable'] = host.mutable
host_res['id'] = host.id
host_res['switch_ip'] = host.machine.switch.ip
host_res['link'] = {
"href": '/'.join((endpoint, str(host.id))),
"rel": "self"}

View File

@ -5,27 +5,47 @@ from compass.api import util
class ObjectDoesNotExist(Exception):
"""Define the exception for referring non-existing object"""
pass
def __init__(self, message):
self.message = message
def __str__(self):
return repr(self.message)
class UserInvalidUsage(Exception):
"""Define the exception for fault usage of users"""
pass
def __init__(self, message):
self.message = message
def __str__(self):
return repr(self.message)
class ObjectDuplicateError(Exception):
"""Define the duplicated object exception"""
pass
def __init__(self, message):
self.message = message
def __str__(self):
return repr(self.message)
class InputMissingError(Exception):
"""Define the insufficient input exception"""
pass
def __init__(self, message):
self.message = message
def __str__(self):
return repr(self.message)
class MethodNotAllowed(Exception):
"""Define the exception which invalid method is called"""
pass
def __init__(self, message):
self.message = message
def __str__(self):
return repr(self.message)
@app.errorhandler(ObjectDoesNotExist)

View File

@ -274,9 +274,7 @@ def valid_host_config(config):
try:
validator = valid_format[key]
except:
error_msg = ("Cannot find the path '%s'. Please check the keywords"
% key)
raise errors.UserInvalidUsage(error_msg)
continue
else:
value = flat_config[key]
if validator:
@ -302,15 +300,19 @@ def flatten_dict(dictionary, output, flat_key=""):
output[tmp] = dictionary[key]
def update_dict_value(searchkey, newvalue, dictionary):
def update_dict_value(searchkey, dictionary):
"""Update dictionary value"""
keywords = dictionary.keys()
for key in keywords:
if key == searchkey:
dictionary[key] = newvalue
if isinstance(dictionary[key], str):
dictionary[key] = ""
elif isinstance(dictionary[key], list):
dictionary[key] = []
elif isinstance(dictionary[key], dict):
update_dict_value(searchkey, newvalue, dictionary[key])
update_dict_value(searchkey, dictionary[key])
else:
continue

View File

@ -18,6 +18,7 @@ from compass.db import database
from compass.db.model import Switch
from compass.db.model import Machine
from compass.db.model import Cluster
from compass.db.model import ClusterState
from compass.db.model import ClusterHost
from compass.db.model import HostState
from compass.db.model import Adapter
@ -337,8 +338,27 @@ class TestClusterAPI(ApiTestCase):
super(TestClusterAPI, self).setUp()
#Prepare testing data
with database.session() as session:
cluster = Cluster(name='cluster_01')
session.add(cluster)
clusters_list = [
Cluster(name='cluster_01'), # undeployed
Cluster(name="cluster_02"), # undeployed
Cluster(name="cluster_03", mutable=False), # installing
Cluster(name="cluster_04", mutable=False), # installing
Cluster(name="cluster_05"), # failed
Cluster(name="cluster_06"), # failed
Cluster(name="cluster_07"), # successful
Cluster(name="cluster_08"), # successful
]
session.add_all(clusters_list)
cluster_states = [
ClusterState(id=3, state='INSTALLING'),
ClusterState(id=4, state='INSTALLING'),
ClusterState(id=5, state='ERROR'),
ClusterState(id=6, state='ERROR'),
ClusterState(id=7, state='READY'),
ClusterState(id=8, state='READY'),
]
session.add_all(cluster_states)
session.flush()
def tearDown(self):
@ -367,7 +387,7 @@ class TestClusterAPI(ApiTestCase):
# Create a cluster
def test_post_cluster(self):
# a. Post a new cluster but no adapter exists
cluster_req = {'cluster': {'name': 'cluster_02',
cluster_req = {'cluster': {'name': 'cluster_09',
'adapter_id': 1}}
url = '/clusters'
rv = self.app.post(url, data=json.dumps(cluster_req))
@ -382,8 +402,8 @@ class TestClusterAPI(ApiTestCase):
session.add(adapter)
rv = self.app.post(url, data=json.dumps(cluster_req))
data = json.loads(rv.get_data())
self.assertEqual(data['cluster']['id'], 2)
self.assertEqual(data['cluster']['name'], 'cluster_02')
self.assertEqual(data['cluster']['id'], 9)
self.assertEqual(data['cluster']['name'], 'cluster_09')
#c. Post an existing cluster, return 409
rv = self.app.post(url, data=json.dumps(cluster_req))
@ -392,22 +412,38 @@ class TestClusterAPI(ApiTestCase):
cluster_req['cluster']['name'] = ''
rv = self.app.post(url, data=json.dumps(cluster_req))
data = json.loads(rv.get_data())
self.assertEqual(data['cluster']['id'], 3)
self.assertEqual(data['cluster']['id'], 10)
def test_get_clusters(self):
#Insert more clusters in db
with database.session() as session:
clusters_list = [
Cluster(name="cluster_02"),
Cluster(name="cluster_03"),
Cluster(name="cluster_04")]
session.add_all(clusters_list)
session.flush()
# a. get all clusters
url = "/clusters"
rv = self.app.get(url)
data = json.loads(rv.get_data())
self.assertEqual(len(data['clusters']), 4)
self.assertEqual(len(data['clusters']), 8)
# b. get all undeployed clusters
url = "/clusters?state=undeployed"
rv = self.app.get(url)
data = json.loads(rv.get_data())
self.assertEqual(len(data['clusters']), 2)
# c. get all failed clusters
url = "/clusters?state=failed"
rv = self.app.get(url)
data = json.loads(rv.get_data())
self.assertEqual(len(data['clusters']), 2)
# d. get all installing clusters
url = "/clusters?state=installing"
rv = self.app.get(url)
data = json.loads(rv.get_data())
self.assertEqual(len(data['clusters']), 2)
# e. get all successful clusters
url = "/clusters?state=successful"
rv = self.app.get(url)
data = json.loads(rv.get_data())
self.assertEqual(len(data['clusters']), 2)
def test_put_cluster_security_resource(self):
# Prepare testing data
@ -530,7 +566,7 @@ class TestClusterAPI(ApiTestCase):
from sqlalchemy import func
#Prepare testing data: create machines, clusters in database
#The first three machines will belong to cluster_01, the last one
#belongs to cluster_02
#belongs to cluster_10
with database.session() as session:
machines = [Machine(mac='00:27:88:0c:01'),
Machine(mac='00:27:88:0c:02'),
@ -540,11 +576,11 @@ class TestClusterAPI(ApiTestCase):
Machine(mac='00:27:88:0c:06'),
Machine(mac='00:27:88:0c:07'),
Machine(mac='00:27:88:0c:08')]
clusters = [Cluster(name='cluster_02')]
clusters = [Cluster(name='cluster_10')]
session.add_all(machines)
session.add_all(clusters)
# add a host to machine '00:27:88:0c:04' to cluster_02
host = ClusterHost(cluster_id=2, machine_id=4,
host = ClusterHost(cluster_id=10, machine_id=4,
hostname='host_c2_01')
session.add(host)
@ -676,28 +712,36 @@ class ClusterHostAPITest(ApiTestCase):
"networking": {
"interfaces": {
"management": {
"ip": "192.168.1.1"}},
"ip": "192.168.1.1"},
"tenant": {
"ip": "10.12.1.1"}
},
"global": {}},
"roles": ""}
"roles": []}
# Insert a host into database for testing
with database.session() as session:
clusters_list = [Cluster(name='cluster_01'),
Cluster(name='cluster_02')]
session.add_all(clusters_list)
machines_list = [Machine(mac='00:27:88:0c:01'),
Machine(mac='00:27:88:0c:02'),
Machine(mac='00:27:88:0c:03'),
Machine(mac='00:27:88:0c:04')]
switch = Switch(ip='192.168.1.1')
session.add(switch)
machines_list = [Machine(mac='00:27:88:0c:01', switch_id=1),
Machine(mac='00:27:88:0c:02', switch_id=1),
Machine(mac='00:27:88:0c:03', switch_id=1),
Machine(mac='00:27:88:0c:04', switch_id=1)]
session.add_all(machines_list)
host = ClusterHost(hostname='host_01', cluster_id=1, machine_id=1)
host.config_data = json.dumps(self.test_config_data)
session.add(host)
hosts_list = [
ClusterHost(hostname='host_02', cluster_id=1, machine_id=2),
ClusterHost(hostname='host_03', cluster_id=1, machine_id=3),
ClusterHost(hostname='host_04', cluster_id=2, machine_id=4)
]
host = ClusterHost(hostname='host_01', cluster_id=1, machine_id=1)
host.config_data = json.dumps(self.test_config_data)
session.add(host)
session.add_all(hosts_list)
def tearDown(self):
@ -725,12 +769,15 @@ class ClusterHostAPITest(ApiTestCase):
expected_config['networking']['interfaces']['management']['mac'] \
= "00:27:88:0c:01"
expected_config['switch_port'] = ''
expected_config['switch_ip'] = '192.168.1.1'
expected_config['vlan'] = 0
self.assertDictEqual(config, expected_config)
def test_clusterHost_put_config(self):
config = deepcopy(self.test_config_data)
config['roles'] = ['base']
config['networking']['interfaces']['management']['ip'] = '192.168.1.2'
config['networking']['interfaces']['tenant']['ip'] = '10.12.1.2'
# 1. Try to put a config of the cluster host which does not exist
url = '/clusterhosts/1000/config'
@ -738,11 +785,11 @@ class ClusterHostAPITest(ApiTestCase):
self.assertEqual(404, rv.status_code)
# 2. Config with incorrect ip format
url = '/clusterhosts/1/config'
config2 = deepcopy(self.test_config_data)
config2['hostname'] = 'host_01_01'
config2['networking']['interfaces']['management']['ip'] = 'xxx'
rv = self.app.put(url, data=json.dumps(config2))
url = '/clusterhosts/2/config'
incorrect_config = deepcopy(config)
incorrect_config['hostname'] = 'host_02_02'
incorrect_config['networking']['interfaces']['management']['ip'] = 'xxx'
rv = self.app.put(url, data=json.dumps(incorrect_config))
self.assertEqual(400, rv.status_code)
# 3. Config put sucessfully
@ -750,7 +797,8 @@ class ClusterHostAPITest(ApiTestCase):
self.assertEqual(200, rv.status_code)
with database.session() as session:
config_db = session.query(ClusterHost.config_data)\
.filter_by(id=1).first()[0]
.filter_by(id=2).first()[0]
self.maxDiff = None
self.assertDictEqual(config, json.loads(config_db))
def test_clusterHost_delete_subkey(self):
@ -760,11 +808,10 @@ class ClusterHostAPITest(ApiTestCase):
self.assertEqual(400, rv.status_code)
# 2. Try to delete a subkey sucessfully
url = 'clusterhosts/1/config/ip'
url = 'clusterhosts/1/config/roles'
rv = self.app.delete(url)
self.assertEqual(200, rv.status_code)
expected_config = deepcopy(self.test_config_data)
expected_config['networking']['interfaces']['management']['ip'] = ''
with database.session() as session:
config_db = session.query(ClusterHost.config_data).filter_by(id=1)\
.first()[0]
@ -774,7 +821,7 @@ class ClusterHostAPITest(ApiTestCase):
with database.session() as session:
session.query(ClusterHost).filter_by(id=1)\
.update({'mutable': False})
url = 'clusterhosts/1/config/ip'
url = 'clusterhosts/1/config/roles'
rv = self.app.delete(url)
self.assertEqual(400, rv.status_code)