Commit 46f2aac9 authored by Xavier Thompson's avatar Xavier Thompson

Include sharedness in service info

See merge request nexedi/slapos.core!516
parents ab87a9cd f1d84e5f
...@@ -106,6 +106,7 @@ def do_info(logger, conf, local): ...@@ -106,6 +106,7 @@ def do_info(logger, conf, local):
info = { info = {
'software-url': instance._software_release_url, 'software-url': instance._software_release_url,
'software-type': instance._source_reference, 'software-type': instance._source_reference,
'shared': bool(instance._root_slave),
'requested-state': instance._requested_state, 'requested-state': instance._requested_state,
'instance-parameters': instance._parameter_dict, 'instance-parameters': instance._parameter_dict,
'connection-parameters': connection_parameter_dict, 'connection-parameters': connection_parameter_dict,
......
...@@ -1037,6 +1037,29 @@ def busy_root_partitions_list(title=None): ...@@ -1037,6 +1037,29 @@ def busy_root_partitions_list(title=None):
partitions.append(p) partitions.append(p)
return partitions return partitions
def busy_root_shared_list(title=None):
shared = []
query = 'SELECT * FROM %s WHERE asked_by==""'
args = []
if title:
query += ' AND reference=?'
args.append('_' + title)
for row in execute_db('slave', query, args):
host = execute_db('partition', 'SELECT * FROM %s WHERE reference=?', [row['hosted_by']], one=True)
if not host:
continue
for slave_dict in loads(host['slave_instance_list'].encode('utf-8')):
if slave_dict['slave_reference'] == row['reference']:
break
else:
continue
s = {}
s['url_string'] = host['software_release']
s['title'] = row['reference'][1:] # root shared are prefixed with _
s['relative_url'] = url_for('hateoas_shared', shared_reference=s['title'])
shared.append(s)
return shared
def computers_list(reference=None): def computers_list(reference=None):
computers = [] computers = []
query = 'SELECT * FROM %s' query = 'SELECT * FROM %s'
...@@ -1066,11 +1089,14 @@ p_computer_info = p_computer_list + ' AND reference:=' ...@@ -1066,11 +1089,14 @@ p_computer_info = p_computer_list + ' AND reference:='
def parse_query(query): def parse_query(query):
if query == p_service_list: if query == p_service_list:
return busy_root_partitions_list() return busy_root_partitions_list() + busy_root_shared_list()
elif query.startswith(p_service_info): elif query.startswith(p_service_info):
title = query[len(p_service_info):] title = query[len(p_service_info):]
if is_valid(title): if is_valid(title):
return busy_root_partitions_list(title.strip('"')) partition = busy_root_partitions_list(title.strip('"'))
if partition:
return partition
return busy_root_shared_list(title.strip('"'))
elif query == p_computer_list: elif query == p_computer_list:
return computers_list() return computers_list()
elif query.startswith(p_computer_info): elif query.startswith(p_computer_info):
...@@ -1084,44 +1110,81 @@ def hateoas_partitions(partition_reference): ...@@ -1084,44 +1110,81 @@ def hateoas_partitions(partition_reference):
partition = execute_db('partition', 'SELECT * FROM %s WHERE partition_reference=?', [partition_reference], one=True) partition = execute_db('partition', 'SELECT * FROM %s WHERE partition_reference=?', [partition_reference], one=True)
if partition is None: if partition is None:
abort(404) abort(404)
partition['reference']=partition['partition_reference']
return hateoas_service_document(**partition, shared=0)
@app.route('/hateoas/shared/<shared_reference>', methods=['GET'])
def hateoas_shared(shared_reference):
slave_reference = '_' + shared_reference # root shared are prefixed with _ in db
shared = execute_db('slave', 'SELECT * FROM %s WHERE reference=?', [slave_reference], one=True)
if shared is None:
abort(404)
partition = execute_db('partition', 'SELECT * FROM %s WHERE reference=?', [shared['hosted_by']], one=True)
if partition is None:
abort(404)
slave_list = loads(partition['slave_instance_list'].encode('utf-8'))
for slave_dict in slave_list:
if slave_dict['slave_reference'] == slave_reference:
break
else:
abort(404)
del slave_dict['slave_title'], slave_dict['slave_reference']
software_type = slave_dict.pop('slap_software_type')
xml = dict2xml(slave_dict)
return hateoas_service_document(
reference = shared_reference,
requested_state='unused',
xml=xml,
connection_xml=shared['connection_xml'],
software_release=partition['software_release'],
software_type=software_type,
shared=1,
)
def hateoas_service_document(**kw):
# my_slap_state corresponds to requested_state, not slap_state. # my_slap_state corresponds to requested_state, not slap_state.
return { return {
'_embedded': { '_embedded': {
'_view': { '_view': {
'form_id': { 'form_id': {
'type': 'StringField', 'type': 'StringField',
'key': 'partition', 'key': 'form_id',
'default': partition['reference'], 'default': 'InstanceTree_viewAsHateoas',
}, },
'my_reference': { 'my_reference': {
'type': 'StringField', 'type': 'StringField',
'key': 'field_my_reference', 'key': 'field_my_reference',
'default': partition['partition_reference'], 'default': kw['reference'],
}, },
'my_slap_state': { 'my_slap_state': {
'type': 'StringField', 'type': 'StringField',
'key': 'field_my_slap_state', 'key': 'field_my_slap_state',
'default': partition['requested_state'], 'default': kw['requested_state'],
}, },
'my_text_content': { 'my_text_content': {
'type': 'StringField', 'type': 'StringField',
'key': 'field_my_text_content', 'key': 'field_my_text_content',
'default': partition['xml'], 'default': kw['xml'],
}, },
'my_connection_parameter_list': { 'my_connection_parameter_list': {
'type': 'StringField', 'type': 'StringField',
'key': 'field_my_connection_parameter_list', 'key': 'field_my_connection_parameter_list',
'default': partition['connection_xml'], 'default': kw['connection_xml'],
}, },
'my_url_string': { 'my_url_string': {
'type': 'StringField', 'type': 'StringField',
'key': 'field_my_url_string', 'key': 'field_my_url_string',
'default': partition['software_release'], 'default': kw['software_release'],
}, },
'my_source_reference': { 'my_source_reference': {
'type': 'StringField', 'type': 'StringField',
'key': 'field_my_source_reference', 'key': 'field_my_source_reference',
'default': partition['software_type'], 'default': kw['software_type'],
},
'my_root_slave': {
'type': 'IntegerField',
'key': 'field_my_root_slave',
'default': kw['shared'],
}, },
}, },
}, },
......
...@@ -663,6 +663,7 @@ class TestCliInfo(CliMixin): ...@@ -663,6 +663,7 @@ class TestCliInfo(CliMixin):
instance = slapos.slap.SoftwareInstance( instance = slapos.slap.SoftwareInstance(
_software_release_url='SR1', _software_release_url='SR1',
_source_reference='mytype', _source_reference='mytype',
_root_slave=False,
_requested_state='mystate', _requested_state='mystate',
_connection_dict=conn_params, _connection_dict=conn_params,
_parameter_dict={'myinstanceparameter': 'value2'}) _parameter_dict={'myinstanceparameter': 'value2'})
...@@ -684,6 +685,7 @@ class TestCliInfo(CliMixin): ...@@ -684,6 +685,7 @@ class TestCliInfo(CliMixin):
expected = { expected = {
"software-url": instance._software_release_url, "software-url": instance._software_release_url,
"software-type": instance._source_reference, "software-type": instance._source_reference,
'shared': instance._root_slave,
"requested-state": instance._requested_state, "requested-state": instance._requested_state,
"instance-parameters": instance._parameter_dict, "instance-parameters": instance._parameter_dict,
"connection-parameters": { "connection-parameters": {
......
...@@ -451,7 +451,7 @@ class MasterMixin(BasicMixin, unittest.TestCase): ...@@ -451,7 +451,7 @@ class MasterMixin(BasicMixin, unittest.TestCase):
app = self.app app = self.app
class TestConnectionHelper: class TestConnectionHelper:
def GET(self, path, params=None, headers=None): def GET(self, path, params=None, headers=None):
return app.get(path, query_string=params, data=data).data return app.get(path, query_string=params, headers=headers).data
def POST(self, path, params=None, data=None, def POST(self, path, params=None, data=None,
content_type='application/x-www-form-urlencoded'): content_type='application/x-www-form-urlencoded'):
...@@ -1217,8 +1217,9 @@ database_uri = %(rootdir)s/lib/proxy.db ...@@ -1217,8 +1217,9 @@ database_uri = %(rootdir)s/lib/proxy.db
) )
def startProxy(self): def startProxy(self):
logfile = os.path.join(self._rootdir, 'proxy.log')
self.proxy_process = self.cliDoSlapos( self.proxy_process = self.cliDoSlapos(
('proxy', 'start'), ('proxy', 'start', '--log-file', logfile),
method=subprocess.Popen, method=subprocess.Popen,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
...@@ -1288,6 +1289,16 @@ class TestCliInformation(CliMasterMixin): ...@@ -1288,6 +1289,16 @@ class TestCliInformation(CliMasterMixin):
json.loads(output), json.loads(output),
{'MyInstance0': 'http://sr0//', 'MyInstance1': 'http://sr1//', 'MyInstance2': 'http://sr2//'}) {'MyInstance0': 'http://sr0//', 'MyInstance1': 'http://sr1//', 'MyInstance2': 'http://sr2//'})
def test_service_list_with_shared(self):
self.format_for_number_of_partitions(1)
self.request('http://sr0//', None, 'MyHostInstance0', None)
self.request('http://sr0//', None, 'MySharedInstance1', None, shared=True)
self.request('http://sr0//', None, 'MySharedInstance2', None, shared=True)
output = self.cliDoSlapos(('service', 'list'), stderr=subprocess.DEVNULL)
self.assertEqual(
json.loads(output),
{'MyHostInstance0': 'http://sr0//', 'MySharedInstance1': 'http://sr0//', 'MySharedInstance2': 'http://sr0//'})
def test_service_info(self): def test_service_info(self):
self.format_for_number_of_partitions(3) self.format_for_number_of_partitions(3)
self.request('http://sr0//', None, 'MyInstance0', None) self.request('http://sr0//', None, 'MyInstance0', None)
...@@ -1299,6 +1310,7 @@ class TestCliInformation(CliMasterMixin): ...@@ -1299,6 +1310,7 @@ class TestCliInformation(CliMasterMixin):
{ {
"software-url": "http://sr0//", "software-url": "http://sr0//",
"software-type": "default", "software-type": "default",
"shared": False,
"requested-state": "started", "requested-state": "started",
"instance-parameters": {}, "instance-parameters": {},
"connection-parameters": {}, "connection-parameters": {},
...@@ -1311,6 +1323,7 @@ class TestCliInformation(CliMasterMixin): ...@@ -1311,6 +1323,7 @@ class TestCliInformation(CliMasterMixin):
{ {
"software-url": "http://sr1//", "software-url": "http://sr1//",
"software-type": "MyType1", "software-type": "MyType1",
"shared": False,
"requested-state": "started", "requested-state": "started",
"instance-parameters": {"couscous": "hello"}, "instance-parameters": {"couscous": "hello"},
"connection-parameters": {}, "connection-parameters": {},
...@@ -1323,6 +1336,38 @@ class TestCliInformation(CliMasterMixin): ...@@ -1323,6 +1336,38 @@ class TestCliInformation(CliMasterMixin):
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
self.assertIn('Instance MyInstance2 does not exist.', e.output) self.assertIn('Instance MyInstance2 does not exist.', e.output)
def test_service_info_with_shared(self):
self.format_for_number_of_partitions(1)
self.request('http://sr0//', 'MyType1', 'MyHostInstance0', None)
self.request('http://sr0//', 'MyType1', 'MySharedInstance1', None, shared=True, partition_parameter_kw={'couscous': 'hello'})
self.request('http://sr0//', 'MyType1', 'MySharedInstance2', None, shared=True, partition_parameter_kw={'couscous': 'bye'})
output0 = self.cliDoSlapos(('service', 'info', 'MySharedInstance1'), stderr=subprocess.DEVNULL)
self.assertEqual(
json.loads(output0),
{
"software-url": "http://sr0//",
"software-type": "MyType1",
"shared": True,
"requested-state": "unused",
"instance-parameters": {"couscous": "hello"},
"connection-parameters": {},
"status": "unsupported",
},
)
output1 = self.cliDoSlapos(('service', 'info', 'MySharedInstance2'), stderr=subprocess.DEVNULL)
self.assertEqual(
json.loads(output1),
{
"software-url": "http://sr0//",
"software-type": "MyType1",
"shared": True,
"requested-state": "unused",
"instance-parameters": {"couscous": "bye"},
"connection-parameters": {},
"status": "unsupported",
},
)
def test_invalid_service_names(self): def test_invalid_service_names(self):
invalid_names = ('"MyInstance0', 'MyInstance1"', 'My"Instance2', 'title:="MyInstance3"', 'reference:="MyInstance4"') invalid_names = ('"MyInstance0', 'MyInstance1"', 'My"Instance2', 'title:="MyInstance3"', 'reference:="MyInstance4"')
self.format_for_number_of_partitions(len(invalid_names)) self.format_for_number_of_partitions(len(invalid_names))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment