Commit 06c32a51 authored by Vivien Alger's avatar Vivien Alger

merge conflict: revision change

parents 53b4f6bf e2f5d561
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
import random\n import random\n
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery\n
person = context\n person = context\n
\n \n
computer_partition = None\n computer_partition = None\n
...@@ -75,17 +76,17 @@ else:\n ...@@ -75,17 +76,17 @@ else:\n
explicit_location = False\n explicit_location = False\n
if "computer_guid" in filter_kw:\n if "computer_guid" in filter_kw:\n
explicit_location = True\n explicit_location = True\n
query_kw["parent_reference"] = filter_kw.pop("computer_guid")\n query_kw["parent_reference"] = SimpleQuery(parent_reference=filter_kw.pop("computer_guid"))\n
\n \n
if "instance_guid" in filter_kw:\n if "instance_guid" in filter_kw:\n
explicit_location = True\n explicit_location = True\n
portal = context.getPortalObject()\n portal = context.getPortalObject()\n
instance_guid = filter_kw.pop("instance_guid")\n instance_guid = filter_kw.pop("instance_guid")\n
query_kw["aggregate_related_reference"] = instance_guid\n query_kw["aggregate_related_reference"] = SimpleQuery(aggregate_related_reference=instance_guid)\n
\n \n
if \'network_guid\' in filter_kw:\n if \'network_guid\' in filter_kw:\n
network_guid = filter_kw.pop(\'network_guid\')\n network_guid = filter_kw.pop(\'network_guid\')\n
query_kw["default_subordination_reference"] = network_guid\n query_kw["default_subordination_reference"] = SimpleQuery(default_subordination_reference=network_guid)\n
\n \n
computer_base_category_list = [\n computer_base_category_list = [\n
\'group\',\n \'group\',\n
......
...@@ -1914,6 +1914,26 @@ class TestSlapOSCoreSoftwareInstanceRequest(testSlapOSMixin): ...@@ -1914,6 +1914,26 @@ class TestSlapOSCoreSoftwareInstanceRequest(testSlapOSMixin):
requested_instance2.getSlaXml()) requested_instance2.getSlaXml())
self.assertEqual(bang_amount+1, self._countBang(requested_instance)) self.assertEqual(bang_amount+1, self._countBang(requested_instance))
def test_update_connection_bang_requester(self):
request_kw = self.request_kw.copy()
request_kw['software_title'] = self.generateNewSoftwareTitle()
self.software_instance.requestInstance(**request_kw)
requested_instance = self.software_instance.REQUEST.get(
'request_instance')
self.tic()
bang_amount = self._countBang(self.software_instance)
connection_xml = self.generateSafeXml()
requested_instance.updateConnection(connection_xml=connection_xml)
transaction.commit()
self.assertEqual(bang_amount+1, self._countBang(self.software_instance))
class TestSlapOSCorePersonRequest(testSlapOSMixin): class TestSlapOSCorePersonRequest(testSlapOSMixin):
def afterSetUp(self): def afterSetUp(self):
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>instance = state_change[\'object\']\n
portal = instance.getPortalObject()\n
\n
for requester_instance in portal.portal_catalog(\n
portal_type="Software Instance",\n
default_predecessor_uid=instance.getUid()):\n
requester_instance.getObject().bang(\n
bang_tree=False,\n
comment="%s parameters changed" % instance.getRelativeUrl())\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>RequestedInstance_bangResquesterInstance</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</item> </item>
<item> <item>
<key> <string>script_name</string> </key> <key> <string>script_name</string> </key>
<value> <string></string> </value> <value> <string>RequestedInstance_bangResquesterInstance</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -80,7 +80,7 @@ else:\n ...@@ -80,7 +80,7 @@ else:\n
"type": "application/vnd.slapos.org.hal+json; class=slapos.org.computer" },\n "type": "application/vnd.slapos.org.hal+json; class=slapos.org.computer" },\n
\n \n
"http://slapos.org/reg/software": {\n "http://slapos.org/reg/software": {\n
"href": "%s/Computer_getHateoasSoftwareList" % context.absolute_url(),\n "href": "%s/Computer_getHateoasSoftwareInstallationList" % context.absolute_url(),\n
"type": "application/vnd.slapos.org.hal+json; class=slapos.org.collection",\n "type": "application/vnd.slapos.org.hal+json; class=slapos.org.collection",\n
},\n },\n
\n \n
......
...@@ -80,12 +80,13 @@ else:\n ...@@ -80,12 +80,13 @@ else:\n
\n \n
for sql_obj in context.getPortalObject().portal_catalog(\n for sql_obj in context.getPortalObject().portal_catalog(\n
portal_type=\'Software Installation\',\n portal_type=\'Software Installation\',\n
aggregate_relative_url=context.getRelativeUrl(),\n default_aggregate_uid=context.getUid(),\n
validation_state=\'validated\',\n
):\n ):\n
obj = sql_obj.getObject()\n obj = sql_obj.getObject()\n
result_dict[\'_links\'][\'item\'].append({\n result_dict[\'_links\'][\'item\'].append({\n
\'href\': \'%s/Software_getHateoas\' % obj.absolute_url(),\n \'href\': \'%s/SoftwareInstallation_getHateoas\' % obj.absolute_url(),\n
\'type\': \'application/vnd.slapos.org.hal+json; class=slapos.org.software\',\n \'type\': \'application/vnd.slapos.org.hal+json; class=slapos.org.software_installation\',\n
})\n })\n
\n \n
response.setHeader(\'Content-Type\', type)\n response.setHeader(\'Content-Type\', type)\n
...@@ -98,7 +99,7 @@ else:\n ...@@ -98,7 +99,7 @@ else:\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Computer_getHateoasSoftwareList</string> </value> <value> <string>Computer_getHateoasSoftwareInstallationList</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -80,7 +80,7 @@ else:\n ...@@ -80,7 +80,7 @@ else:\n
\n \n
for sql_obj in context.getPortalObject().portal_catalog(\n for sql_obj in context.getPortalObject().portal_catalog(\n
portal_type="Computer",\n portal_type="Computer",\n
source_administration_uid=context.getUid(),\n default_source_administration_uid=context.getUid(),\n
):\n ):\n
obj = sql_obj.getObject()\n obj = sql_obj.getObject()\n
result_dict[\'_links\'][\'item\'].append({\n result_dict[\'_links\'][\'item\'].append({\n
......
...@@ -56,7 +56,7 @@ if REQUEST is None:\n ...@@ -56,7 +56,7 @@ if REQUEST is None:\n
raise Unauthorized\n raise Unauthorized\n
\n \n
response = REQUEST.RESPONSE\n response = REQUEST.RESPONSE\n
type = \'application/vnd.slapos.org.hal+json; class=slapos.org.software\'\n type = \'application/vnd.slapos.org.hal+json; class=slapos.org.software_installation\'\n
\n \n
if REQUEST.other[\'method\'] != "GET":\n if REQUEST.other[\'method\'] != "GET":\n
response.setStatus(405)\n response.setStatus(405)\n
...@@ -78,15 +78,17 @@ else:\n ...@@ -78,15 +78,17 @@ else:\n
\n \n
import json\n import json\n
result_dict = {\n result_dict = {\n
\'_class\': \'slapos.org.software\',\n \'_class\': \'slapos.org.software_installation\',\n
\'title\': context.getTitle(),\n \'title\': context.getTitle(),\n
\'software_url\': context.getUrlString(),\n
\'status\': state,\n \'status\': state,\n
\'_links\': {\n \'_links\': {\n
"self": { "href": context.Base_getRequestUrl(),\n "self": { "href": context.Base_getRequestUrl(),\n
"type": "application/vnd.slapos.org.hal+json; class=slapos.org.software" },\n "type": "application/vnd.slapos.org.hal+json; class=slapos.org.software_installation" },\n
},\n },\n
}\n }\n
url_string = context.getUrlString(None)\n
if url_string is not None:\n
result_dict["_links"]["software_release"] = { "href": url_string }\n
\n \n
response.setHeader(\'Content-Type\', type)\n response.setHeader(\'Content-Type\', type)\n
return json.dumps(result_dict)\n return json.dumps(result_dict)\n
...@@ -98,7 +100,7 @@ else:\n ...@@ -98,7 +100,7 @@ else:\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Software_getHateoas</string> </value> <value> <string>SoftwareInstallation_getHateoas</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -322,7 +322,7 @@ class TestSlapOSHypermediaPersonScenario(testSlapOSMixin): ...@@ -322,7 +322,7 @@ class TestSlapOSHypermediaPersonScenario(testSlapOSMixin):
# Get user's software # Get user's software
##################################################### #####################################################
content_type = "application/vnd.slapos.org.hal+json; " \ content_type = "application/vnd.slapos.org.hal+json; " \
"class=slapos.org.software" "class=slapos.org.software_installation"
software_link_dict = software_collection_hal['_links']\ software_link_dict = software_collection_hal['_links']\
['item'][0] ['item'][0]
......
...@@ -327,6 +327,12 @@ class TestSlapOSPerson_getHateoas(testSlapOSMixin): ...@@ -327,6 +327,12 @@ class TestSlapOSPerson_getHateoas(testSlapOSMixin):
"method": "POST", "method": "POST",
"type": "application/json; class=slapos.org.hosting_subscription", "type": "application/json; class=slapos.org.hosting_subscription",
}, },
"http://slapos.org/reg/computer": {
"href": "%s/Person_getHateoasComputerList" % \
person_user.absolute_url(),
"type": "application/vnd.slapos.org.hal+json; " \
"class=slapos.org.collection",
},
"http://slapos.org/reg/hosting_subscription": { "http://slapos.org/reg/hosting_subscription": {
"href": "%s/Person_getHateoasHostingSubscriptionList" % \ "href": "%s/Person_getHateoasHostingSubscriptionList" % \
person_user.absolute_url(), person_user.absolute_url(),
...@@ -921,30 +927,7 @@ class TestSlapOSInstance_getHateoasNews(testSlapOSMixin): ...@@ -921,30 +927,7 @@ class TestSlapOSInstance_getHateoasNews(testSlapOSMixin):
}, },
}))) })))
class ComputerAndSoftwareMixin(testSlapOSMixin): class TestSlapOSPerson_getHateoasComputerList(testSlapOSMixin):
script_name = None
def afterSetUp(self):
self.logout()
self.login('ERP5TypeTestCase')
self.erp5_person = self._makePerson()
#self.logout()
self.login(self.erp5_person.getReference())
self.portal.portal_slap.requestComputer(
"computer %s" % self.erp5_person.getReference())
self.tic()
self.computer = self.portal.portal_catalog(portal_type="Computer",
sort_on=[('creation_date','descending')])[0].getObject()
self.tic()
self.portal.portal_slap.supplySupply("http://foo.com/software.cfg",
self.computer.getReference(), "available")
self.tic()
self.software_installation = self.portal.portal_catalog(
portal_type="Software Installation",
aggregate_relative_url=self.computer.getRelativeUrl())[0].getObject()
def _makePerson(self): def _makePerson(self):
new_id = self.generateNewId() new_id = self.generateNewId()
...@@ -962,66 +945,62 @@ class ComputerAndSoftwareMixin(testSlapOSMixin): ...@@ -962,66 +945,62 @@ class ComputerAndSoftwareMixin(testSlapOSMixin):
self.tic() self.tic()
return person_user return person_user
def test_REQUEST_mandatory(self): def _makeComputer(self):
computer = self.portal.computer_module\
.template_computer.Base_createCloneDocument(batch_mode=1)
computer.validate()
return computer
def test_getHateoasComputerList_REQUEST_mandatory(self):
self.assertRaises( self.assertRaises(
Unauthorized, Unauthorized,
getattr(self.portal, self.script_name) self.portal.Person_getHateoasComputerList
) )
@simulate('Base_getRequestHeader', '*args, **kwargs', @simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd+bar"') 'return "application/vnd+bar"')
def test_wrong_ACCEPT(self): def test_getHateoasComputerList_wrong_ACCEPT(self):
fake_request = do_fake_request("GET") fake_request = do_fake_request("GET")
result = getattr(self.portal, self.script_name)(REQUEST=fake_request) result = self.portal.Person_getHateoasComputerList(REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 406) self.assertEquals(fake_request.RESPONSE.status, 406)
self.assertEquals(result, "") self.assertEquals(result, "")
def test_bad_method(self): @simulate('Base_getRequestHeader', '*args, **kwargs',
@simulate('Base_getRequestHeader', '*args, **kwargs', 'return "application/vnd.slapos.org.hal+json; ' \
'return "application/vnd.slapos.org.hal+json; ' \ 'class=slapos.org.collection"')
'class=' + self.json_class + '"') def test_getHateoasComputerList_bad_method(self):
def check_bad_method(self): fake_request = do_fake_request("POST")
fake_request = do_fake_request("POST") result = self.portal.Person_getHateoasComputerList(
result = getattr(self.portal, self.script_name)(REQUEST=fake_request) REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 405) self.assertEquals(fake_request.RESPONSE.status, 405)
self.assertEquals(result, "") self.assertEquals(result, "")
check_bad_method(self)
@simulate('Base_getRequestHeader', '*args, **kwargs',
def test_request_not_correct_context(self): 'return "application/vnd.slapos.org.hal+json; ' \
@simulate('Base_getRequestHeader', '*args, **kwargs', 'class=slapos.org.collection"')
'return "application/vnd.slapos.org.hal+json; ' \ def test_getHateoasComputerList_request_not_correct_context(self):
'class=' + self.json_class + '"') fake_request = do_fake_request("GET")
def check_not_correct_context(self): result = self.portal.Person_getHateoasComputerList(REQUEST=fake_request)
fake_request = do_fake_request("GET") self.assertEquals(fake_request.RESPONSE.status, 403)
result = getattr(self.portal, self.script_name)(REQUEST=fake_request) self.assertEquals(result, "")
self.assertEquals(fake_request.RESPONSE.status, 403)
self.assertEquals(result, "") @simulate('Base_getRequestUrl', '*args, **kwargs',
check_not_correct_context(self) 'return "http://example.org/foo"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd.slapos.org.hal+json; ' \
def checkResult(self, context, expected_data): 'class=slapos.org.collection"')
@simulate('Base_getRequestUrl', '*args, **kwargs', def test_getHateoasComputerList_result(self):
'return "http://example.org/foo"') person_user = self._makePerson()
@simulate('Base_getRequestHeader', '*args, **kwargs', computer = self._makeComputer()
'return "application/vnd.slapos.org.hal+json; ' \ computer.edit(source_administration_value=person_user)
'class=' + self.json_class + '"') self.tic()
def check(self): fake_request = do_fake_request("GET")
fake_request = do_fake_request("GET") result = person_user.Person_getHateoasComputerList(REQUEST=fake_request)
result = getattr(context, self.script_name)(REQUEST=fake_request) self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.status, 200) self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'), "application/vnd.slapos.org.hal+json; class=slapos.org.collection"
"application/vnd.slapos.org.hal+json; class=" + self.json_class )
) self.assertEquals(json.loads(result), json.loads(json.dumps({
self.assertEquals(result, json.dumps(expected_data))
check(self)
class TestSlapOSPerson_getHateoasComputerList(ComputerAndSoftwareMixin):
script_name = "Person_getHateoasComputerList"
json_class = "slapos.org.collection"
def test_result(self):
self.checkResult(self.erp5_person, {
'_class': 'slapos.org.collection', '_class': 'slapos.org.collection',
'_links': { '_links': {
"self": { "self": {
...@@ -1031,22 +1010,71 @@ class TestSlapOSPerson_getHateoasComputerList(ComputerAndSoftwareMixin): ...@@ -1031,22 +1010,71 @@ class TestSlapOSPerson_getHateoasComputerList(ComputerAndSoftwareMixin):
}, },
"item": [{ "item": [{
"href": "%s/Computer_getHateoas" % \ "href": "%s/Computer_getHateoas" % \
self.computer.absolute_url(), computer.absolute_url(),
"type": "application/vnd.slapos.org.hal+json; " \ "type": "application/vnd.slapos.org.hal+json; " \
"class=slapos.org.computer" "class=slapos.org.computer"
}], }],
}, },
}) })))
class TestSlapOSComputer_getHateoas(ComputerAndSoftwareMixin): class TestSlapOSComputer_getHateoas(testSlapOSMixin):
script_name = "Computer_getHateoas" def _makeComputer(self):
json_class = "slapos.org.computer" computer = self.portal.computer_module\
.template_computer.Base_createCloneDocument(batch_mode=1)
computer.validate()
self.tic()
return computer
def test_getHateoasComputerList_REQUEST_mandatory(self):
self.assertRaises(
Unauthorized,
self.portal.Computer_getHateoas
)
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd+bar"')
def test_getHateoas_wrong_ACCEPT(self):
fake_request = do_fake_request("GET")
result = self.portal.Computer_getHateoas(REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 406)
self.assertEquals(result, "")
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.computer"')
def test_getHateoas_bad_method(self):
fake_request = do_fake_request("POST")
result = self.portal.Computer_getHateoas(
REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 405)
self.assertEquals(result, "")
def test_result(self): @simulate('Base_getRequestHeader', '*args, **kwargs',
self.checkResult(self.computer, { 'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.computer"')
def test_getHateoas_request_not_correct_context(self):
fake_request = do_fake_request("GET")
result = self.portal.Computer_getHateoas(REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 403)
self.assertEquals(result, "")
@simulate('Base_getRequestUrl', '*args, **kwargs',
'return "http://example.org/foo"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.computer"')
def test_getHateoas_result(self):
computer = self._makeComputer()
fake_request = do_fake_request("GET")
result = computer.Computer_getHateoas(REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
"application/vnd.slapos.org.hal+json; class=slapos.org.computer"
)
self.assertEquals(json.loads(result), json.loads(json.dumps({
'_class': 'slapos.org.computer', '_class': 'slapos.org.computer',
'title': self.computer.getTitle(), 'title': computer.getTitle(),
'_links': { '_links': {
"self": { "self": {
"href": "http://example.org/foo", "href": "http://example.org/foo",
...@@ -1054,21 +1082,81 @@ class TestSlapOSComputer_getHateoas(ComputerAndSoftwareMixin): ...@@ -1054,21 +1082,81 @@ class TestSlapOSComputer_getHateoas(ComputerAndSoftwareMixin):
"class=slapos.org.computer" "class=slapos.org.computer"
}, },
"http://slapos.org/reg/software": { "http://slapos.org/reg/software": {
"href": "%s/Computer_getHateoasSoftwareList" % \ "href": "%s/Computer_getHateoasSoftwareInstallationList" % \
self.computer.absolute_url(), computer.absolute_url(),
"type": "application/vnd.slapos.org.hal+json; " \ "type": "application/vnd.slapos.org.hal+json; " \
"class=slapos.org.collection" "class=slapos.org.collection"
}, },
}, },
}) })))
class TestSlapOSComputer_getHateoasSoftwareInstallationList(testSlapOSMixin):
def _makeComputer(self):
computer = self.portal.computer_module\
.template_computer.Base_createCloneDocument(batch_mode=1)
computer.validate()
return computer
class TestSlapOSComputer_getSoftwareList(ComputerAndSoftwareMixin): def _makeSoftwareInstallation(self):
software_installation = self.portal.software_installation_module\
.template_software_installation.Base_createCloneDocument(batch_mode=1)
software_installation.validate()
return software_installation
script_name = "Computer_getHateoasSoftwareList" def test_getSoftwareInstallationList_REQUEST_mandatory(self):
json_class = "slapos.org.collection" self.assertRaises(
Unauthorized,
self.portal.Computer_getHateoasSoftwareInstallationList
)
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd+bar"')
def test_getSoftwareInstallationList_wrong_ACCEPT(self):
fake_request = do_fake_request("GET")
result = self.portal.Computer_getHateoasSoftwareInstallationList(
REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 406)
self.assertEquals(result, "")
def test_result(self): @simulate('Base_getRequestHeader', '*args, **kwargs',
self.checkResult(self.computer, { 'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.collection"')
def test_getSoftwareInstallationList_bad_method(self):
fake_request = do_fake_request("POST")
result = self.portal.Computer_getHateoasSoftwareInstallationList(
REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 405)
self.assertEquals(result, "")
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.collection"')
def test_getSoftwareInstallationList_request_not_correct_context(self):
fake_request = do_fake_request("GET")
result = self.portal.Computer_getHateoasSoftwareInstallationList(
REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 403)
self.assertEquals(result, "")
@simulate('Base_getRequestUrl', '*args, **kwargs',
'return "http://example.org/foo"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.collection"')
def test_getSoftwareInstallationList_result(self):
computer = self._makeComputer()
software_installation = self._makeSoftwareInstallation()
software_installation.edit(aggregate_value=computer)
self.tic()
fake_request = do_fake_request("GET")
result = computer.Computer_getHateoasSoftwareInstallationList(
REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
"application/vnd.slapos.org.hal+json; class=slapos.org.collection"
)
self.assertEquals(json.loads(result), json.loads(json.dumps({
'_class': 'slapos.org.collection', '_class': 'slapos.org.collection',
'_links': { '_links': {
"self": { "self": {
...@@ -1077,30 +1165,84 @@ class TestSlapOSComputer_getSoftwareList(ComputerAndSoftwareMixin): ...@@ -1077,30 +1165,84 @@ class TestSlapOSComputer_getSoftwareList(ComputerAndSoftwareMixin):
"class=slapos.org.collection" "class=slapos.org.collection"
}, },
"item": [{ "item": [{
"href": "%s/Software_getHateoas" % \ "href": "%s/SoftwareInstallation_getHateoas" % \
self.software_installation.absolute_url(), software_installation.absolute_url(),
"type": "application/vnd.slapos.org.hal+json; " \ "type": "application/vnd.slapos.org.hal+json; " \
"class=slapos.org.software" "class=slapos.org.software_installation"
}], }],
}, },
}) })))
class TestSlapOSSoftwareInstallation_getHateoas(testSlapOSMixin):
def _makeSoftwareInstallation(self):
software_installation = self.portal.software_installation_module\
.template_software_installation.Base_createCloneDocument(batch_mode=1)
software_installation.validate()
return software_installation
def test_getHateoas_REQUEST_mandatory(self):
self.assertRaises(
Unauthorized,
self.portal.SoftwareInstallation_getHateoas
)
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd+bar"')
def test_getHateoas_wrong_ACCEPT(self):
fake_request = do_fake_request("GET")
result = self.portal.SoftwareInstallation_getHateoas(REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 406)
self.assertEquals(result, "")
class TestSlapOSSoftware_getHateoas(ComputerAndSoftwareMixin): @simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.software_installation"')
def test_getHateoas_bad_method(self):
fake_request = do_fake_request("POST")
result = self.portal.SoftwareInstallation_getHateoas(
REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 405)
self.assertEquals(result, "")
script_name = "Software_getHateoas" @simulate('Base_getRequestHeader', '*args, **kwargs',
json_class = "slapos.org.software" 'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.software_installation"')
def test_getHateoas_request_not_correct_context(self):
fake_request = do_fake_request("GET")
result = self.portal.SoftwareInstallation_getHateoas(REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 403)
self.assertEquals(result, "")
def test_result(self): @simulate('Base_getRequestUrl', '*args, **kwargs',
self.checkResult(self.software_installation, { 'return "http://example.org/foo"')
'_class': 'slapos.org.software', @simulate('Base_getRequestHeader', '*args, **kwargs',
'title': self.software_installation.getTitle(), 'return "application/vnd.slapos.org.hal+json; ' \
'class=slapos.org.software_installation"')
def test_getHateoas_result(self):
software_installation = self._makeSoftwareInstallation()
software_installation.edit(url_string="http://foo.com/software.cfg")
self.portal.portal_workflow._jumpToStateFor(software_installation,
'start_requested')
fake_request = do_fake_request("GET")
result = software_installation.SoftwareInstallation_getHateoas(
REQUEST=fake_request)
self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
"application/vnd.slapos.org.hal+json; class=slapos.org.software_installation"
)
self.assertEquals(json.loads(result), json.loads(json.dumps({
'_class': 'slapos.org.software_installation',
'title': software_installation.getTitle(),
'status': 'started', 'status': 'started',
'software_url': "http://foo.com/software.cfg",
'_links': { '_links': {
"self": { "self": {
"href": "http://example.org/foo", "href": "http://example.org/foo",
"type": "application/vnd.slapos.org.hal+json; " \ "type": "application/vnd.slapos.org.hal+json; " \
"class=slapos.org.software" "class=slapos.org.software_installation"
},
"software_release": {
"href": "http://foo.com/software.cfg",
}, },
}, },
}) })))
3 4
\ No newline at end of file \ No newline at end of file
...@@ -54,6 +54,7 @@ download-binary-dir-url = http://www.shacache.org/shadir ...@@ -54,6 +54,7 @@ download-binary-dir-url = http://www.shacache.org/shadir
# Yingjie Xu # Yingjie Xu
# Gabriel Monnerat # Gabriel Monnerat
# Lukasz Nowak # Lukasz Nowak
# Marco Mariani
# Test Agent Signature # Test Agent Signature
signature-certificate-list = signature-certificate-list =
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
...@@ -148,6 +149,19 @@ signature-certificate-list = ...@@ -148,6 +149,19 @@ signature-certificate-list =
Vd0Ozh79JSRpkrdI8R/NRQ2XPHAo+29TT70= Vd0Ozh79JSRpkrdI8R/NRQ2XPHAo+29TT70=
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIJAL9FOtBJZBqAMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCENPTVAtOTIyMCAXDTEyMDkyNjE2MDkwM1oYDzIxMTIwOTAyMTYwOTAzWjAT
MREwDwYDVQQDDAhDT01QLTkyMjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
wlGVd6qOsc2xrtAQ5/rpflzS14/84SE/joaujMw2GGk6EFVSOcqKLq1TnHxkcCkv
nv1NYRPK/hpQOIKcGC1f+DvXXpMJI29R/rt2b2/y1oolxXonSVigBtCQlSyDoOFN
6LBX84CI5aYMvy3mqJCvfGEFBaPqze/PVugq9IpgZg0CAwEAAaNQME4wHQYDVR0O
BBYEFJ7HWyzVKkeSYnSK4FIwcdyng/tRMB8GA1UdIwQYMBaAFJ7HWyzVKkeSYnSK
4FIwcdyng/tRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAZQWob2ki
ie6h90FgSctozXrotb8NIis2MtLIj+WonE0wSqYefxwBmAGjB9cfWz/sNamhM4rn
BP1A2ojVhF6hOE1qvTP5YxcGXOoYTrEQSuDF1hn12WlA4vqIAz1f+4CiMJNlXPwh
7N+X2kvRpHdXAHkBOxX3j34AeCZrSpu/yDQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIJAKRvzcy7OH0UMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV MIIB9jCCAV+gAwIBAgIJAKRvzcy7OH0UMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCENPTVAtNzcyMCAXDTEyMDgxMDE1NDI1MVoYDzIxMTIwNzE3MTU0MjUxWjAT BAMMCENPTVAtNzcyMCAXDTEyMDgxMDE1NDI1MVoYDzIxMTIwNzE3MTU0MjUxWjAT
MREwDwYDVQQDDAhDT01QLTc3MjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA MREwDwYDVQQDDAhDT01QLTc3MjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
......
...@@ -10,7 +10,7 @@ import sys ...@@ -10,7 +10,7 @@ import sys
import prettytable import prettytable
from slapos.grid import networkcache from slapos.grid import networkcache
from slapos.grid.distribution import patched_linux_distribution from slapos.grid.distribution import distribution_tuple
from slapos.cli.config import ConfigCommand from slapos.cli.config import ConfigCommand
...@@ -76,7 +76,7 @@ def do_lookup(logger, cache_dir, software_url): ...@@ -76,7 +76,7 @@ def do_lookup(logger, cache_dir, software_url):
pt = prettytable.PrettyTable(['distribution', 'version', 'id', 'compatible?']) pt = prettytable.PrettyTable(['distribution', 'version', 'id', 'compatible?'])
linux_distribution = patched_linux_distribution() linux_distribution = distribution_tuple()
for os in ostable: for os in ostable:
compatible = 'yes' if networkcache.os_matches(os, linux_distribution) else 'no' compatible = 'yes' if networkcache.os_matches(os, linux_distribution) else 'no'
......
...@@ -23,7 +23,7 @@ class Command(cliff.command.Command): ...@@ -23,7 +23,7 @@ class Command(cliff.command.Command):
def must_be_root(func): def must_be_root(func):
@functools.wraps(func) @functools.wraps(func)
def inner(self, *args, **kw): def inner(self, *args, **kw):
if os.getuid() != 0: if sys.platform != 'cygwin' and os.getuid() != 0:
self.app.log.error('This slapos command must be run as root.') self.app.log.error('This slapos command must be run as root.')
sys.exit(5) sys.exit(5)
return func(self, *args, **kw) return func(self, *args, **kw)
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import argparse import argparse
import codecs
import collections import collections
import locale
import logging import logging
import sys import sys
...@@ -114,6 +116,30 @@ class SlapOSApp(cliff.app.App): ...@@ -114,6 +116,30 @@ class SlapOSApp(cliff.app.App):
command_manager=SlapOSCommandManager('slapos.cli'), command_manager=SlapOSCommandManager('slapos.cli'),
) )
def _set_streams(self, stdin, stdout, stderr):
try:
# SlapOS: might fail in some systems
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
pass
if sys.version_info[:2] == (2, 6):
# Configure the input and output streams. If a stream is
# provided, it must be configured correctly by the
# caller. If not, make sure the versions of the standard
# streams used by default are wrapped with encodings. This
# works around a problem with Python 2.6 fixed in 2.7 and
# later (http://hg.python.org/cpython/rev/e60ef17561dc/).
lang, encoding = locale.getdefaultlocale()
encoding = getattr(sys.stdout, 'encoding', None) or encoding
self.stdin = stdin or codecs.getreader(encoding)(sys.stdin)
self.stdout = stdout or codecs.getwriter(encoding)(sys.stdout)
self.stderr = stderr or codecs.getwriter(encoding)(sys.stderr)
else:
self.stdin = stdin or sys.stdin
self.stdout = stdout or sys.stdout
self.stderr = stderr or sys.stderr
def build_option_parser(self, *args, **kw): def build_option_parser(self, *args, **kw):
kw.setdefault('argparse_kwargs', {}) kw.setdefault('argparse_kwargs', {})
kw['argparse_kwargs']['conflict_handler'] = 'resolve' kw['argparse_kwargs']['conflict_handler'] = 'resolve'
......
...@@ -57,6 +57,8 @@ class FormatCommand(ConfigCommand): ...@@ -57,6 +57,8 @@ class FormatCommand(ConfigCommand):
help="Don't actually do anything" help="Don't actually do anything"
" (default: %(default)s)") " (default: %(default)s)")
ap.add_argument('-c', '--console',
help="Console output (obsolete)")
return ap return ap
@must_be_root @must_be_root
......
...@@ -844,7 +844,7 @@ class Interface(object): ...@@ -844,7 +844,7 @@ class Interface(object):
def addIPv4LocalAddress(self, addr=None): def addIPv4LocalAddress(self, addr=None):
"""Adds local IPv4 address in ipv4_local_network""" """Adds local IPv4 address in ipv4_local_network"""
netmask = '255.255.255.254' if sys.platform == 'cygwin' \ netmask = str(netaddr.IPNetwork(self.ipv4_local_network).netmask) if sys.platform == 'cygwin' \
else '255.255.255.255' else '255.255.255.255'
local_address_list = self.getIPv4LocalAddressList() local_address_list = self.getIPv4LocalAddressList()
if addr is None: if addr is None:
...@@ -1211,7 +1211,7 @@ class FormatConfig(object): ...@@ -1211,7 +1211,7 @@ class FormatConfig(object):
# check root # check root
# XXX in the new CLI, this is checked by the @must_be_root decorator. # XXX in the new CLI, this is checked by the @must_be_root decorator.
if root_needed and os.getuid() != 0: if sys.platform != 'cygwin' and root_needed and os.getuid() != 0:
message = "Root rights are needed" message = "Root rights are needed"
self.logger.error(message) self.logger.error(message)
sys.stderr.write(message + '\n') sys.stderr.write(message + '\n')
......
...@@ -220,7 +220,8 @@ class Software(object): ...@@ -220,7 +220,8 @@ class Software(object):
# but you can change it for development purposes. # but you can change it for development purposes.
[buildout] [buildout]
extends = {url} extends =
{url}
""".format(url=url))) """.format(url=url)))
self._set_ownership(buildout_cfg) self._set_ownership(buildout_cfg)
......
...@@ -13,6 +13,9 @@ patched_linux_distribution(...): ...@@ -13,6 +13,9 @@ patched_linux_distribution(...):
see http://bugs.python.org/issue9514 see http://bugs.python.org/issue9514
otherwise, Ubuntu will always be reported as an unstable Debian, regardless of the version. otherwise, Ubuntu will always be reported as an unstable Debian, regardless of the version.
distribution_tuple()
returns a (distname, version, id) tuple under linux or cygwin
""" """
import platform import platform
...@@ -62,3 +65,10 @@ def patched_linux_distribution(distname='', version='', id='', ...@@ -62,3 +65,10 @@ def patched_linux_distribution(distname='', version='', id='',
pass pass
return platform.linux_distribution(distname, version, id, supported_dists, full_distribution_name) return platform.linux_distribution(distname, version, id, supported_dists, full_distribution_name)
def distribution_tuple():
if platform.system().startswith('CYGWIN_'):
return (platform.system(), platform.platform(), '')
else:
return patched_linux_distribution()
...@@ -18,7 +18,7 @@ import platform ...@@ -18,7 +18,7 @@ import platform
import shutil import shutil
import traceback import traceback
from slapos.grid.distribution import os_matches, patched_linux_distribution from slapos.grid.distribution import os_matches, distribution_tuple
try: try:
try: try:
...@@ -88,7 +88,7 @@ def download_network_cached(cache_url, dir_url, software_url, software_root, ...@@ -88,7 +88,7 @@ def download_network_cached(cache_url, dir_url, software_url, software_root,
if tags.get('machine') != platform.machine(): if tags.get('machine') != platform.machine():
continue continue
if not os_matches(ast.literal_eval(tags.get('os')), if not os_matches(ast.literal_eval(tags.get('os')),
patched_linux_distribution()): distribution_tuple()):
continue continue
if tags.get('software_url') != software_url: if tags.get('software_url') != software_url:
continue continue
...@@ -134,7 +134,7 @@ def upload_network_cached(software_root, software_url, cached_key, ...@@ -134,7 +134,7 @@ def upload_network_cached(software_root, software_url, cached_key,
software_url=software_url, software_url=software_url,
software_root=software_root, software_root=software_root,
machine=platform.machine(), machine=platform.machine(),
os=str(patched_linux_distribution()) os=str(distribution_tuple())
) )
f = open(path, 'r') f = open(path, 'r')
......
...@@ -627,6 +627,11 @@ class Slapgrid(object): ...@@ -627,6 +627,11 @@ class Slapgrid(object):
logger=self.logger) logger=self.logger)
computer_partition_state = computer_partition.getState() computer_partition_state = computer_partition.getState()
# XXX this line breaks 37 tests
# self.logger.info(' Instance type: %s' % computer_partition.getType())
self.logger.info(' Instance status: %s' % computer_partition_state)
if computer_partition_state == COMPUTER_PARTITION_STARTED_STATE: if computer_partition_state == COMPUTER_PARTITION_STARTED_STATE:
local_partition.install() local_partition.install()
computer_partition.available() computer_partition.available()
...@@ -635,6 +640,8 @@ class Slapgrid(object): ...@@ -635,6 +640,8 @@ class Slapgrid(object):
computer_partition.started() computer_partition.started()
elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE: elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE:
try: try:
# We want to process the partition, even if stopped, because it should
# propagate the state to children if any.
local_partition.install() local_partition.install()
computer_partition.available() computer_partition.available()
finally: finally:
......
...@@ -222,6 +222,11 @@ class IComputerPartition(IBuildoutController, IRequester): ...@@ -222,6 +222,11 @@ class IComputerPartition(IBuildoutController, IRequester):
The contained values are connection parameters of a compute partition. The contained values are connection parameters of a compute partition.
""" """
def getType():
"""
Returns the Software Type of the instance.
"""
def setUsage(usage_log): def setUsage(usage_log):
""" """
Associate a usage log to the computer partition. Associate a usage log to the computer partition.
......
...@@ -482,6 +482,18 @@ class ComputerPartition(SlapRequester): ...@@ -482,6 +482,18 @@ class ComputerPartition(SlapRequester):
raise ResourceNotReady() raise ResourceNotReady()
return self._requested_state return self._requested_state
def getType(self):
"""
return the Software Type of the instance.
Raise RessourceNotReady if not present.
"""
# XXX: software type should not belong to the parameter dict.
software_type = self.getInstanceParameterDict().get(
'slap_software_type', None)
if not software_type:
raise ResourceNotReady()
return software_type
def getInstanceParameterDict(self): def getInstanceParameterDict(self):
return getattr(self, '_parameter_dict', None) or {} return getattr(self, '_parameter_dict', None) or {}
......
...@@ -270,7 +270,7 @@ class TestComputer(SlapformatMixin): ...@@ -270,7 +270,7 @@ class TestComputer(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'ip addr list bridge', 'ip addr list bridge',
'groupadd slapsoft', 'groupadd slapsoft',
'useradd -d /software_root -g slapsoft -s /bin/false slapsoft -r' 'useradd -d /software_root -g slapsoft slapsoft -r'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -308,7 +308,7 @@ class TestComputer(SlapformatMixin): ...@@ -308,7 +308,7 @@ class TestComputer(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'ip addr list bridge', 'ip addr list bridge',
'groupadd slapsoft', 'groupadd slapsoft',
'useradd -d /software_root -g slapsoft -s /bin/false slapsoft -r' 'useradd -d /software_root -g slapsoft slapsoft -r'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -363,9 +363,9 @@ class TestComputer(SlapformatMixin): ...@@ -363,9 +363,9 @@ class TestComputer(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'ip addr list bridge', 'ip addr list bridge',
'groupadd slapsoft', 'groupadd slapsoft',
'useradd -d /software_root -g slapsoft -s /bin/false slapsoft -r', 'useradd -d /software_root -g slapsoft slapsoft -r',
'groupadd testuser', 'groupadd testuser',
'useradd -d /instance_root/partition -g testuser -s /bin/false -G slapsoft testuser -r', 'useradd -d /instance_root/partition -g testuser -G slapsoft testuser -r',
'tunctl -t tap -u testuser', 'tunctl -t tap -u testuser',
'ip link set tap up', 'ip link set tap up',
'brctl show', 'brctl show',
...@@ -452,9 +452,9 @@ class TestComputer(SlapformatMixin): ...@@ -452,9 +452,9 @@ class TestComputer(SlapformatMixin):
self.assertEqual([ self.assertEqual([
# 'ip addr list bridge', # 'ip addr list bridge',
'groupadd slapsoft', 'groupadd slapsoft',
'useradd -d /software_root -g slapsoft -s /bin/false slapsoft -r', 'useradd -d /software_root -g slapsoft slapsoft -r',
'groupadd testuser', 'groupadd testuser',
'useradd -d /instance_root/partition -g testuser -s /bin/false -G slapsoft testuser -r', 'useradd -d /instance_root/partition -g testuser -G slapsoft testuser -r',
# 'ip addr add ip/255.255.255.255 dev bridge', # 'ip addr add ip/255.255.255.255 dev bridge',
# 'ip addr list bridge', # 'ip addr list bridge',
# 'ip addr add ip/ffff:ffff:ffff:ffff:: dev bridge', # 'ip addr add ip/ffff:ffff:ffff:ffff:: dev bridge',
...@@ -533,7 +533,7 @@ class TestUser(SlapformatMixin): ...@@ -533,7 +533,7 @@ class TestUser(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'groupadd doesnotexistsyet', 'groupadd doesnotexistsyet',
'useradd -d /doesnotexistsyet -g doesnotexistsyet -s /bin/false '\ 'useradd -d /doesnotexistsyet -g doesnotexistsyet '\
'doesnotexistsyet -r' 'doesnotexistsyet -r'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -546,7 +546,7 @@ class TestUser(SlapformatMixin): ...@@ -546,7 +546,7 @@ class TestUser(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'groupadd doesnotexistsyet', 'groupadd doesnotexistsyet',
'useradd -d /doesnotexistsyet -g doesnotexistsyet -s /bin/false -G '\ 'useradd -d /doesnotexistsyet -g doesnotexistsyet -G '\
'additionalgroup1,additionalgroup2 doesnotexistsyet -r' 'additionalgroup1,additionalgroup2 doesnotexistsyet -r'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -559,7 +559,7 @@ class TestUser(SlapformatMixin): ...@@ -559,7 +559,7 @@ class TestUser(SlapformatMixin):
user.create() user.create()
self.assertEqual([ self.assertEqual([
'useradd -d /testuser -g testuser -s /bin/false testuser -r' 'useradd -d /testuser -g testuser testuser -r'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -573,7 +573,7 @@ class TestUser(SlapformatMixin): ...@@ -573,7 +573,7 @@ class TestUser(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'groupadd testuser', 'groupadd testuser',
'usermod -d /testuser -g testuser -s /bin/false -G '\ 'usermod -d /testuser -g testuser -G '\
'additionalgroup1,additionalgroup2 testuser' 'additionalgroup1,additionalgroup2 testuser'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -587,7 +587,7 @@ class TestUser(SlapformatMixin): ...@@ -587,7 +587,7 @@ class TestUser(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'groupadd testuser', 'groupadd testuser',
'usermod -d /testuser -g testuser -s /bin/false testuser' 'usermod -d /testuser -g testuser testuser'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -601,7 +601,7 @@ class TestUser(SlapformatMixin): ...@@ -601,7 +601,7 @@ class TestUser(SlapformatMixin):
user.create() user.create()
self.assertEqual([ self.assertEqual([
'usermod -d /testuser -g testuser -s /bin/false testuser' 'usermod -d /testuser -g testuser testuser'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
......
version = '1.0.0rc4' version = '1.0.0rc5'
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