runnertest.py 19 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
# vim: set et sts=2:
Marco Mariani's avatar
Marco Mariani committed
3
# pylint: disable-msg=W0311,C0301,C0103,C0111,R0904
4

5
import argparse
6
import ConfigParser
Marco Mariani's avatar
Marco Mariani committed
7
import datetime
Marco Mariani's avatar
Marco Mariani committed
8
import hashlib
9
import json
Marco Mariani's avatar
Marco Mariani committed
10 11
import os
import shutil
12
import time
Marco Mariani's avatar
Marco Mariani committed
13 14
import unittest

15 16 17
from slapos.runner.utils import (getProfilePath, getSession, isInstanceRunning,
                                 isSoftwareRunning, startProxy)
from slapos.runner.process import killRunningProcess, isRunning
Marco Mariani's avatar
Marco Mariani committed
18
from slapos.runner import views
19
import slapos.slap
Marco Mariani's avatar
Marco Mariani committed
20

Marco Mariani's avatar
Marco Mariani committed
21

Marco Mariani's avatar
Marco Mariani committed
22 23 24 25
#Helpers
def loadJson(response):
  return json.loads(response.data)

26 27

class Config:
Marco Mariani's avatar
Marco Mariani committed
28 29 30 31 32 33
  def __init__(self):
    self.runner_workdir = None
    self.software_root = None
    self.instance_root = None
    self.configuration_file_path = None

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
  def setConfig(self):
    """
    Set options given by parameters.
    """
    self.configuration_file_path = os.path.abspath(os.environ.get('CONFIG_FILE_PATH'))

    # Load configuration file
    configuration_parser = ConfigParser.SafeConfigParser()
    configuration_parser.read(self.configuration_file_path)
    # Merges the arguments and configuration

    for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
                    "sshkeys_authority", "gitclient", "cloud9_IDE"):
      configuration_dict = dict(configuration_parser.items(section))
      for key in configuration_dict:
        if not getattr(self, key, None):
          setattr(self, key, configuration_dict[key])

Marco Mariani's avatar
Marco Mariani committed
52

53 54 55 56 57 58 59 60
class SlaprunnerTestCase(unittest.TestCase):

  def setUp(self):
    """Initialize slapos webrunner here"""
    views.app.config['TESTING'] = True
    self.users = ["slapuser", "slappwd", "slaprunner@nexedi.com", "SlapOS web runner"]
    self.updateUser = ["newslapuser", "newslappwd", "slaprunner@nexedi.com", "SlapOS web runner"]
    self.rcode = "41bf2657"
61
    self.repo = 'http://git.erp5.org/repos/slapos.git'
Marco Mariani's avatar
Marco Mariani committed
62 63
    self.software = "workspace/slapos/software/"  # relative directory fo SR
    self.project = 'slapos'  # Default project name
64 65 66
    self.template = 'template.cfg'
    self.partitionPrefix = 'slappart'
    self.slaposBuildout = "1.6.0-dev-SlapOS-010"
67 68 69 70
    #create slaprunner configuration
    config = Config()
    config.setConfig()
    workdir = os.path.join(config.runner_workdir, 'project')
71
    software_link = os.path.join(config.runner_workdir, 'softwareLink')
72 73
    views.app.config.update(**config.__dict__)
    #update or create all runner base directory to test_dir
74

75 76
    if not os.path.exists(workdir):
      os.mkdir(workdir)
77 78
    if not os.path.exists(software_link):
      os.mkdir(software_link)
79 80 81
    views.app.config.update(
      software_log=config.software_root.rstrip('/') + '.log',
      instance_log=config.instance_root.rstrip('/') + '.log',
Marco Mariani's avatar
Marco Mariani committed
82
      workspace=workdir,
83
      software_link=software_link,
84 85 86
      instance_profile='instance.cfg',
      software_profile='software.cfg',
      SECRET_KEY="123456",
Marco Mariani's avatar
Marco Mariani committed
87
      PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
88 89 90 91
    )
    self.app = views.app.test_client()
    self.app.config = views.app.config
    #Create password recover code
92 93
    with open(os.path.join(views.app.config['etc_dir'], '.rcode'), 'w') as rpwd:
      rpwd.write(self.rcode)
94 95 96 97 98 99

  def tearDown(self):
    """Remove all test data"""
    os.unlink(os.path.join(self.app.config['etc_dir'], '.rcode'))
    project = os.path.join(self.app.config['etc_dir'], '.project')
    users = os.path.join(self.app.config['etc_dir'], '.users')
100

101 102 103 104 105 106 107 108 109 110
    if os.path.exists(users):
      os.unlink(users)
    if os.path.exists(project):
      os.unlink(project)
    if os.path.exists(self.app.config['workspace']):
      shutil.rmtree(self.app.config['workspace'])
    if os.path.exists(self.app.config['software_root']):
      shutil.rmtree(self.app.config['software_root'])
    if os.path.exists(self.app.config['instance_root']):
      shutil.rmtree(self.app.config['instance_root'])
111 112 113 114
    if os.path.exists(self.app.config['software_link']):
      shutil.rmtree(self.app.config['software_link'])
    self.logout()
    #Stop process
115 116 117
    killRunningProcess('slapproxy', recursive=True)
    killRunningProcess('slapgrid-cp', recursive=True)
    killRunningProcess('slapgrid-sr', recursive=True)
118 119

  def configAccount(self, username, password, email, name, rcode):
Marco Mariani's avatar
Marco Mariani committed
120
    """Helper for configAccount"""
Marco Mariani's avatar
Marco Mariani committed
121 122 123 124 125 126 127 128 129
    return self.app.post('/configAccount',
                         data=dict(
                           username=username,
                           password=password,
                           email=email,
                           name=name,
                           rcode=rcode
                         ),
                         follow_redirects=True)
130 131 132

  def login(self, username, password):
    """Helper for Login method"""
Marco Mariani's avatar
Marco Mariani committed
133 134 135 136 137 138
    return self.app.post('/doLogin',
                         data=dict(
                           clogin=username,
                           cpwd=password
                         ),
                         follow_redirects=True)
139 140 141

  def setAccount(self):
    """Initialize user account and log user in"""
Marco Mariani's avatar
Marco Mariani committed
142
    response = loadJson(self.configAccount(self.users[0], self.users[1],
143
                  self.users[2], self.users[3], self.rcode))
Marco Mariani's avatar
Marco Mariani committed
144
    response2 = loadJson(self.login(self.users[0], self.users[1]))
145 146 147 148 149 150 151 152 153
    self.assertEqual(response['result'], "")
    self.assertEqual(response2['result'], "")

  def logout(self):
    """Helper for Logout current user"""
    return self.app.get('/dologout', follow_redirects=True)

  def updateAccount(self, newaccount, rcode):
    """Helper for update user account data"""
Marco Mariani's avatar
Marco Mariani committed
154 155 156 157 158 159 160 161 162
    return self.app.post('/updateAccount',
                         data=dict(
                           username=newaccount[0],
                           password=newaccount[1],
                           email=newaccount[2],
                           name=newaccount[3],
                           rcode=rcode
                         ),
                         follow_redirects=True)
163

164 165 166
  def getCurrentSR(self):
   return getProfilePath(self.app.config['etc_dir'],
                              self.app.config['software_profile'])
167 168

  def proxyStatus(self, status=True, sleep_time=0):
169
    """Helper for testslapproxy status"""
170 171 172 173
    proxy = isRunning('slapproxy')
    if proxy != status and sleep_time != 0:
      time.sleep(sleep_time)
      proxy = isRunning('slapproxy')
174 175
    self.assertEqual(proxy, status)

176
  def setupProjectFolder(self, withSoftware=False):
Marco Mariani's avatar
Marco Mariani committed
177
    """Helper to create a project folder as for slapos.git"""
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    base = os.path.join(self.app.config['workspace'], 'slapos')
    software = os.path.join(base, 'software')
    os.mkdir(base)
    os.mkdir(software)
    if withSoftware:
      testSoftware = os.path.join(software, 'slaprunner-test')
      sr = "[buildout]\n\n"
      sr += "parts = command\n\nunzip = true\nnetworkcache-section = networkcache\n\n"
      sr += "find-links += http://www.nexedi.org/static/packages/source/slapos.buildout/\n\n"
      sr += "[networkcache]\ndownload-cache-url = http://www.shacache.org/shacache"
      sr += "\ndownload-dir-url = http://www.shacache.org/shadir\n\n"
      sr += "[command]\nrecipe = zc.recipe.egg\neggs = plone.recipe.command\n\n"
      sr += "[versions]\nzc.buildout = %s\n" % self.slaposBuildout
      os.mkdir(testSoftware)
      open(os.path.join(testSoftware, self.app.config['software_profile']),
                          'w').write(sr)

  def setupSoftwareFolder(self):
Marco Mariani's avatar
Marco Mariani committed
196
    """Helper to setup compiled software release dir"""
197 198
    self.setupProjectFolder(withSoftware=True)
    md5 = hashlib.md5(os.path.join(self.app.config['workspace'],
Marco Mariani's avatar
Marco Mariani committed
199 200 201
                                   "slapos/software/slaprunner-test",
                                   self.app.config['software_profile'])
                      ).hexdigest()
202 203 204 205 206
    base = os.path.join(self.app.config['software_root'], md5)
    template = os.path.join(base, self.template)
    content = "[buildout]\n"
    content += "parts = \n  create-file\n\n"
    content += "eggs-directory = %s\n" % os.path.join(base, 'eggs')
Marco Mariani's avatar
Marco Mariani committed
207
    content += "develop-eggs-directory = %s\n\n" % os.path.join(base, 'develop-eggs')
208 209 210 211 212 213 214
    content += "[create-file]\nrecipe = plone.recipe.command\n"
    content += "filename = ${buildout:directory}/etc\n"
    content += "command = mkdir ${:filename} && echo 'simple file' > ${:filename}/testfile\n"
    os.mkdir(self.app.config['software_root'])
    os.mkdir(base)
    open(template, "w").write(content)

215 216
  def stopSlapproxy(self):
    """Kill slapproxy process"""
217
    killRunningProcess('slapproxy', recursive=True)
218

219 220
  #Begin test case here
  def test_wrong_login(self):
Marco Mariani's avatar
Marco Mariani committed
221
    """Test Login user before create session. This should return an error value"""
222 223
    response = self.login(self.users[0], self.users[1])
    #redirect to config account page
Cédric Le Ninivin's avatar
Cédric Le Ninivin committed
224
    assert "<h2 class='title'>Your personal information</h2><br/>" in response.data
225 226 227 228 229

  def test_configAccount(self):
    """For the first lauch of slaprunner user need do create first account"""
    result = self.configAccount(self.users[0], self.users[1], self.users[2],
                  self.users[3], self.rcode)
Marco Mariani's avatar
Marco Mariani committed
230
    response = loadJson(result)
231 232 233 234 235 236
    self.assertEqual(response['code'], 1)
    account = getSession(self.app.config)
    self.assertEqual(account, self.users)

  def test_login_logout(self):
    """test login with good and wrong values, test logout"""
Marco Mariani's avatar
Marco Mariani committed
237
    response = loadJson(self.configAccount(self.users[0], self.users[1],
238 239
                  self.users[2], self.users[3], self.rcode))
    self.assertEqual(response['result'], "")
Marco Mariani's avatar
Marco Mariani committed
240
    result = loadJson(self.login(self.users[0], "wrongpwd"))
241
    self.assertEqual(result['result'], "Login or password is incorrect, please check it!")
Marco Mariani's avatar
Marco Mariani committed
242
    resultwr = loadJson(self.login("wronglogin", "wrongpwd"))
243 244
    self.assertEqual(resultwr['result'], "Login or password is incorrect, please check it!")
    #try now with true values
Marco Mariani's avatar
Marco Mariani committed
245
    resultlg = loadJson(self.login(self.users[0], self.users[1]))
246 247 248 249 250 251
    self.assertEqual(resultlg['result'], "")
    #after login test logout
    result = self.logout()
    assert "<h2>Login to Slapos Web Runner</h2>" in result.data

  def test_updateAccount(self):
Marco Mariani's avatar
Marco Mariani committed
252
    """test Update accound, this needs the user to log in"""
253
    self.setAccount()
Marco Mariani's avatar
Marco Mariani committed
254
    response = loadJson(self.updateAccount(self.updateUser, self.rcode))
255 256 257 258
    self.assertEqual(response['code'], 1)
    result = self.logout()
    assert "<h2>Login to Slapos Web Runner</h2>" in result.data
    #retry login with new values
Marco Mariani's avatar
Marco Mariani committed
259
    response = loadJson(self.login(self.updateUser[0], self.updateUser[1]))
260 261 262 263
    self.assertEqual(response['result'], "")
    #log out now!
    self.logout()

264 265
  def test_startProxy(self):
    """Test slapproxy"""
266
    self.proxyStatus(False)
267 268 269
    startProxy(self.app.config)
    self.proxyStatus(True)
    self.stopSlapproxy()
270
    self.proxyStatus(False, sleep_time=1)
271 272 273 274 275

  def test_cloneProject(self):
    """Start scenario 1 for deploying SR: Clone a project from git repository"""
    self.setAccount()
    folder = 'workspace/' + self.project
Marco Mariani's avatar
Marco Mariani committed
276 277 278 279 280 281
    data = {
      'repo': self.repo,
      'user': 'Slaprunner test',
      'email': 'slaprunner@nexedi.com',
      'name': folder
    }
Marco Mariani's avatar
Marco Mariani committed
282
    response = loadJson(self.app.post('/cloneRepository', data=data,
283 284
                    follow_redirects=True))
    self.assertEqual(response['result'], "")
Marco Mariani's avatar
Marco Mariani committed
285
    # Get realpath of create project
286
    path_data = dict(file=folder)
Marco Mariani's avatar
Marco Mariani committed
287
    response = loadJson(self.app.post('/getPath', data=path_data,
288 289 290 291
                    follow_redirects=True))
    self.assertEqual(response['code'], 1)
    realFolder = response['result'].split('#')[0]
    #Check git configuration
292
    config = open(os.path.join(realFolder, '.git/config')).read()
293
    assert "slaprunner@nexedi.com" in config and "Slaprunner test" in config
Marco Mariani's avatar
Marco Mariani committed
294 295

    # Checkout to slaprunner branch, this supposes that branch slaprunner exit
Marco Mariani's avatar
Marco Mariani committed
296 297 298 299 300 301 302
    response = loadJson(self.app.post('/newBranch',
                                      data=dict(
                                        project=folder,
                                        create='0',
                                        name='slaprunner'
                                      ),
                                      follow_redirects=True))
303 304 305 306 307
    self.assertEqual(response['result'], "")
    self.logout()

  def test_createSR(self):
    """Scenario 2: Create a new software release"""
308 309 310
    self.setAccount()
    #setup project directory
    self.setupProjectFolder()
311
    newSoftware = os.path.join(self.software, 'slaprunner-test')
Marco Mariani's avatar
Marco Mariani committed
312
    response = loadJson(self.app.post('/createSoftware',
Marco Mariani's avatar
Marco Mariani committed
313 314
                                      data=dict(folder=newSoftware),
                                      follow_redirects=True))
315 316 317 318 319 320 321 322 323 324
    self.assertEqual(response['result'], "")
    currentSR = self.getCurrentSR()
    assert newSoftware in currentSR
    self.logout()

  def test_openSR(self):
    """Scenario 3: Open software release"""
    self.test_cloneProject()
    #Login
    self.login(self.users[0], self.users[1])
Marco Mariani's avatar
Marco Mariani committed
325
    software = os.path.join(self.software, 'drupal')  # Drupal SR must exist in SR folder
Marco Mariani's avatar
Marco Mariani committed
326
    response = loadJson(self.app.post('/setCurrentProject',
Marco Mariani's avatar
Marco Mariani committed
327 328
                                      data=dict(path=software),
                                      follow_redirects=True))
329 330 331 332 333
    self.assertEqual(response['result'], "")
    currentSR = self.getCurrentSR()
    assert software in currentSR
    self.assertFalse(isInstanceRunning(self.app.config))
    self.assertFalse(isSoftwareRunning(self.app.config))
Marco Mariani's avatar
Marco Mariani committed
334 335 336

    # Slapproxy process is supposed to be started
    # newSoftware = os.path.join(self.software, 'slaprunner-test')
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
    self.proxyStatus(True)
    self.stopSlapproxy()
    self.logout()

  def test_runSoftware(self):
    """Scenario 4: CReate empty SR and save software.cfg file
      then run slapgrid-sr
    """
    #Call config account
    #call create software Release
    self.test_createSR()
    #Login
    self.login(self.users[0], self.users[1])
    newSoftware = self.getCurrentSR()
    softwareRelease = "[buildout]\n\nparts =\n  test-application\n"
    softwareRelease += "#Test download git web repos éè@: utf-8 caracters\n"
    softwareRelease += "[test-application]\nrecipe = hexagonit.recipe.download\n"
    softwareRelease += "url = http://git.erp5.org/gitweb/slapos.git\n"
    softwareRelease += "filename = slapos.git\n"
    softwareRelease += "download-only = true\n"
Alain Takoudjou's avatar
Alain Takoudjou committed
357
    response = loadJson(self.app.post('/saveFileContent',
Marco Mariani's avatar
Marco Mariani committed
358 359 360
                                      data=dict(file=newSoftware,
                                                content=softwareRelease),
                                      follow_redirects=True))
361
    self.assertEqual(response['result'], "")
Marco Mariani's avatar
Marco Mariani committed
362 363 364

    # Compile software and wait until slapgrid ends
    # this is supposed to use current SR
Alain Takoudjou's avatar
Alain Takoudjou committed
365
    response = loadJson(self.app.post('/runSoftwareProfile',
Marco Mariani's avatar
Marco Mariani committed
366 367
                                      data=dict(),
                                      follow_redirects=True))
368 369 370
    self.assertTrue(response['result'])
    self.assertTrue(os.path.exists(self.app.config['software_root']))
    self.assertTrue(os.path.exists(self.app.config['software_log']))
371
    assert "test-application" in open(self.app.config['software_log']).read()
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
    sr_dir = os.listdir(self.app.config['software_root'])
    self.assertEqual(len(sr_dir), 1)
    createdFile = os.path.join(self.app.config['software_root'], sr_dir[0],
                              'parts', 'test-application', 'slapos.git')
    self.assertTrue(os.path.exists(createdFile))
    self.proxyStatus(True)
    self.stopSlapproxy()
    self.logout()

  def test_updateInstanceParameter(self):
    """Scenarion 5: Update parameters of current sofware profile"""
    self.setAccount()
    self.setupSoftwareFolder()
    #Set current projet and run Slapgrid-cp
    software = os.path.join(self.software, 'slaprunner-test')
Alain Takoudjou's avatar
Alain Takoudjou committed
387
    response = loadJson(self.app.post('/setCurrentProject',
Marco Mariani's avatar
Marco Mariani committed
388 389
                                      data=dict(path=software),
                                      follow_redirects=True))
390 391 392 393 394 395 396 397
    self.assertEqual(response['result'], "")
    self.proxyStatus(True)
    #Send paramters for the instance
    parameterDict = dict(appname='slaprunnerTest', cacountry='France')
    parameterXml = '<?xml version="1.0" encoding="utf-8"?>\n<instance>'
    parameterXml += '<parameter id="appname">slaprunnerTest</parameter>\n'
    parameterXml += '<parameter id="cacountry">France</parameter>\n</instance>'
    software_type = 'production'
Alain Takoudjou's avatar
Alain Takoudjou committed
398
    response = loadJson(self.app.post('/saveParameterXml',
Marco Mariani's avatar
Marco Mariani committed
399 400 401
                                      data=dict(parameter=parameterXml,
                                                software_type=software_type),
                                      follow_redirects=True))
402 403 404 405 406 407 408 409
    self.assertEqual(response['result'], "")
    slap = slapos.slap.slap()
    slap.initializeConnection(self.app.config['master_url'])
    computer = slap.registerComputer(self.app.config['computer_id'])
    partitionList = computer.getComputerPartitionList()
    self.assertNotEqual(partitionList, [])
    #Assume that the requested partition is partition 0
    slapParameterDict = partitionList[0].getInstanceParameterDict()
Marco Mariani's avatar
Marco Mariani committed
410 411
    self.assertTrue('appname' in slapParameterDict)
    self.assertTrue('cacountry' in slapParameterDict)
412 413 414 415 416
    self.assertEqual(slapParameterDict['appname'], 'slaprunnerTest')
    self.assertEqual(slapParameterDict['cacountry'], 'France')
    self.assertEqual(slapParameterDict['slap_software_type'], 'production')

    #test getParameterXml for webrunner UI
Alain Takoudjou's avatar
Alain Takoudjou committed
417
    response = loadJson(self.app.get('/getParameterXml/xml'))
418
    self.assertEqual(parameterXml, response['result'])
Alain Takoudjou's avatar
Alain Takoudjou committed
419
    response = loadJson(self.app.get('/getParameterXml/dict'))
420 421 422 423 424 425 426 427 428
    self.assertEqual(parameterDict, response['result']['instance'])
    self.stopSlapproxy()
    self.logout()

  def test_requestInstance(self):
    """Scenarion 6: request software instance"""
    self.test_updateInstanceParameter()
    #Login
    self.login(self.users[0], self.users[1])
429
    self.proxyStatus(False, sleep_time=1)
430
    #run Software profile
Alain Takoudjou's avatar
Alain Takoudjou committed
431
    response = loadJson(self.app.post('/runSoftwareProfile',
Marco Mariani's avatar
Marco Mariani committed
432 433
                                      data=dict(),
                                      follow_redirects=True))
434 435
    self.assertTrue(response['result'])
    #run instance profile
Alain Takoudjou's avatar
Alain Takoudjou committed
436
    response = loadJson(self.app.post('/runInstanceProfile',
Marco Mariani's avatar
Marco Mariani committed
437 438
                                      data=dict(),
                                      follow_redirects=True))
439 440
    self.assertTrue(response['result'])
    #Check that all partitions has been created
441
    assert "create-file" in open(self.app.config['instance_log']).read()
442 443 444 445 446 447 448 449 450 451 452 453
    instanceDir = os.listdir(self.app.config['instance_root'])
    for num in range(int(self.app.config['partition_amount'])):
      partition = os.path.join(self.app.config['instance_root'],
                    self.partitionPrefix + str(num))
      self.assertTrue(os.path.exists(partition))

    #Go to partition 0
    instancePath = os.path.join(self.app.config['instance_root'],
                         self.partitionPrefix + '0')
    createdFile = os.path.join(instancePath, 'etc', 'testfile')
    self.assertTrue(os.path.exists(createdFile))
    assert 'simple file' in open(createdFile).read()
454 455 456
    self.proxyStatus(True)
    self.stopSlapproxy()
    self.logout()
457

Marco Mariani's avatar
Marco Mariani committed
458

459
def main():
460 461 462
  # Empty parser for now - so that erp5testnode is happy when doing --help
  parser = argparse.ArgumentParser()
  parser.parse_args()
463
  unittest.main(module=__name__)
464 465

if __name__ == '__main__':
466
  main()