slapgrid.py 45.3 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
# vim: set et sts=2:
Łukasz Nowak's avatar
Łukasz Nowak committed
3 4
##############################################################################
#
5 6
# Copyright (c) 2010, 2011, 2012 Vifib SARL and Contributors.
# All Rights Reserved.
Łukasz Nowak's avatar
Łukasz Nowak committed
7 8 9 10 11
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
12
# guarantees and support are strongly advised to contract a Free Software
Łukasz Nowak's avatar
Łukasz Nowak committed
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################
30

Łukasz Nowak's avatar
Łukasz Nowak committed
31 32
import os
import pkg_resources
33
import random
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
34 35
import socket
import StringIO
36
import subprocess
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
37 38 39
import sys
import tempfile
import time
40
import stat
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
41
import traceback
Łukasz Nowak's avatar
Łukasz Nowak committed
42
import warnings
43
import logging
44

Łukasz Nowak's avatar
Łukasz Nowak committed
45
if sys.version_info < (2, 6):
Marco Mariani's avatar
Marco Mariani committed
46
  warnings.warn('Used python version (%s) is old and has problems with'
Łukasz Nowak's avatar
Łukasz Nowak committed
47 48
      ' IPv6 connections' % sys.version.split('\n')[0])

Marco Mariani's avatar
Marco Mariani committed
49 50
from lxml import etree

Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
51 52
from slapos.slap.slap import NotFoundError
from slapos.slap.slap import ServerError
53
from slapos.util import mkdir_p, chownDirectory
Marco Mariani's avatar
Marco Mariani committed
54
from slapos.grid.exception import BuildoutFailedError
55
from slapos.grid.SlapObject import Software, Partition
Marco Mariani's avatar
Marco Mariani committed
56
from slapos.grid.svcbackend import launchSupervisord
57
from slapos.grid.utils import (md5digest, createPrivateDirectory, dropPrivileges,
58
                               SlapPopen, updateFile)
Marco Mariani's avatar
Marco Mariani committed
59
import slapos.slap
Łukasz Nowak's avatar
Łukasz Nowak committed
60 61


Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
62
# XXX: should be moved to SLAP library
63
COMPUTER_PARTITION_DESTROYED_STATE = 'destroyed'
64 65
COMPUTER_PARTITION_STARTED_STATE = 'started'
COMPUTER_PARTITION_STOPPED_STATE = 'stopped'
Łukasz Nowak's avatar
Łukasz Nowak committed
66

67 68 69 70
# Global variables about return state of slapgrid
SLAPGRID_SUCCESS = 0
SLAPGRID_FAIL = 1
SLAPGRID_PROMISE_FAIL = 2
71
PROMISE_TIMEOUT = 3
72

73
# XXX hardcoded watchdog_path
74
WATCHDOG_PATH = '/opt/slapos/bin/slapos-watchdog'
75

76
COMPUTER_PARTITION_TIMESTAMP_FILENAME = '.timestamp'
77
COMPUTER_PARTITION_LATEST_BANG_TIMESTAMP_FILENAME = '.slapos_latest_bang_timestamp'
78

79 80 81 82 83

class _formatXMLError(Exception):
  pass


84 85 86
def check_missing_parameters(options):
  required = set([
      'computer_id',
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
87
      # XXX: instance_root is better named "partition_root"
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
      'instance_root',
      'master_url',
      'software_root',
  ])

  if 'key_file' in options:
    required.add('certificate_repository_path')
    required.add('cert_file')
  if 'cert_file' in options:
    required.add('certificate_repository_path')
    required.add('key_file')

  missing = required.difference(options)

  if missing:
    raise RuntimeError('Missing mandatory parameters: %s' % ', '.join(sorted(missing)))

105 106 107 108 109 110 111 112
  # parameter can NOT be empty string or None
  for option in required:
    if not options.get(option):
      missing.add(option)

  if missing:
    raise RuntimeError('Mandatory parameters present but empty: %s' % ', '.join(sorted(missing)))

113

114 115
def check_missing_files(options):
  req_files = [
Marco Mariani's avatar
Marco Mariani committed
116 117 118 119 120 121 122 123 124
      options.get('key_file'),
      options.get('cert_file'),
      options.get('master_ca_file'),
      options.get('shacache-cert-file'),
      options.get('shacache-key-file'),
      options.get('shadir-cert-file'),
      options.get('shadir-key-file'),
      options.get('signature_private_key_file')
  ]
125 126

  req_dirs = [
Marco Mariani's avatar
Marco Mariani committed
127 128
      options.get('certificate_repository_path')
  ]
129 130 131

  for f in req_files:
    if f and not os.path.exists(f):
Marco Mariani's avatar
Marco Mariani committed
132
      raise RuntimeError('File %r does not exist.' % f)
133 134 135 136 137 138

  for d in req_dirs:
    if d and not os.path.isdir(d):
      raise RuntimeError('Directory %r does not exist' % d)


139 140
def merged_options(args, configp):
  options = dict(configp.items('slapos'))
141

142 143
  if configp.has_section('networkcache'):
    options.update(dict(configp.items('networkcache')))
144 145 146 147
  for key, value in vars(args).iteritems():
    if value is not None:
      options[key] = value

Marco Mariani's avatar
Marco Mariani committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161
  if options.get('all'):
    options['develop'] = True

  # Supervisord configuration location
  if not options.get('supervisord_configuration_path'):
    options['supervisord_configuration_path'] = \
      os.path.join(options['instance_root'], 'etc', 'supervisord.conf')
  # Supervisord socket
  if not options.get('supervisord_socket'):
    options['supervisord_socket'] = \
      os.path.join(options['instance_root'], 'supervisord.socket')

  # Parse cache / binary cache options
  # Backward compatibility about "binary-cache-url-blacklist" deprecated option
Marco Mariani's avatar
Marco Mariani committed
162 163
  if (options.get("binary-cache-url-blacklist") and not
        options.get("download-from-binary-cache-url-blacklist")):
Marco Mariani's avatar
Marco Mariani committed
164 165 166 167 168 169 170 171 172
    options["download-from-binary-cache-url-blacklist"] = \
        options["binary-cache-url-blacklist"]
  options["download-from-binary-cache-url-blacklist"] = [
      url.strip() for url in options.get(
          "download-from-binary-cache-url-blacklist", "").split('\n') if url]
  options["upload-to-binary-cache-url-blacklist"] = [
      url.strip() for url in options.get(
          "upload-to-binary-cache-url-blacklist", "").split('\n') if url]

173 174 175
  return options


176
def random_delay(options, logger):
177 178 179 180
  """
  Sleep for a random time to avoid SlapOS Master being DDOSed by an army of
  SlapOS Nodes configured with cron.
  """
181 182
  if options['now']:
    # XXX-Cedric: deprecate '--now'
183 184
    return

185
  maximal_delay = int(options.get('maximal_delay', '0'))
186 187
  if maximal_delay:
    duration = random.randint(1, maximal_delay)
Marco Mariani's avatar
Marco Mariani committed
188 189
    logger.info('Sleeping for %s seconds. To disable this feature, '
                'check --now parameter in slapgrid help.', duration)
190 191 192
    time.sleep(duration)


193
def create_slapgrid_object(options, logger):
194 195 196 197
  signature_certificate_list = None
  if 'signature-certificate-list' in options:
    cert_marker = '-----BEGIN CERTIFICATE-----'
    signature_certificate_list = [
Marco Mariani's avatar
Marco Mariani committed
198 199 200 201
        cert_marker + '\n' + q.strip()
        for q in options['signature-certificate-list'].split(cert_marker)
        if q.strip()
    ]
202 203 204 205 206 207 208 209

  op = options
  return Slapgrid(software_root=op['software_root'],
                  instance_root=op['instance_root'],
                  master_url=op['master_url'],
                  computer_id=op['computer_id'],
                  supervisord_socket=op['supervisord_socket'],
                  supervisord_configuration_path=op['supervisord_configuration_path'],
210 211
                  buildout=op.get('buildout'),
                  logger=logger,
212
                  maximum_periodicity = op.get('maximum_periodicity', 86400),
213 214 215 216 217 218
                  key_file=op.get('key_file'),
                  cert_file=op.get('cert_file'),
                  signature_private_key_file=op.get('signature_private_key_file'),
                  signature_certificate_list=signature_certificate_list,
                  download_binary_cache_url=op.get('download-binary-cache-url'),
                  upload_binary_cache_url=op.get('upload-binary-cache-url'),
Marco Mariani's avatar
Marco Mariani committed
219
                  download_from_binary_cache_url_blacklist=
220
                      op.get('download-from-binary-cache-url-blacklist', []),
Marco Mariani's avatar
Marco Mariani committed
221
                  upload_to_binary_cache_url_blacklist=
222 223 224 225 226
                      op.get('upload-to-binary-cache-url-blacklist', []),
                  upload_cache_url=op.get('upload-cache-url'),
                  download_binary_dir_url=op.get('download-binary-dir-url'),
                  upload_binary_dir_url=op.get('upload-binary-dir-url'),
                  upload_dir_url=op.get('upload-dir-url'),
227 228
                  master_ca_file=op.get('master_ca_file'),
                  certificate_repository_path=op.get('certificate_repository_path'),
229
                  promise_timeout=op.get('promise_timeout', PROMISE_TIMEOUT),
230 231 232 233 234 235 236 237
                  shacache_cert_file=op.get('shacache-cert-file'),
                  shacache_key_file=op.get('shacache-key-file'),
                  shadir_cert_file=op.get('shadir-cert-file'),
                  shadir_key_file=op.get('shadir-key-file'),
                  develop=op.get('develop', False),
                  # Try to fetch from deprecated argument
                  software_release_filter_list=op.get('only-sr', op.get('only_sr')),
                  # Try to fetch from deprecated argument
238
                  computer_partition_filter_list=op.get('only-cp', op.get('only_cp')))
Łukasz Nowak's avatar
Łukasz Nowak committed
239 240


241
def check_required_only_partitions(existing, required):
242 243 244
  """
  Verify the existence of partitions specified by the --only parameter
  """
245 246
  missing = set(required) - set(existing)
  if missing:
Marco Mariani's avatar
Marco Mariani committed
247 248
    plural = ['s', ''][len(missing) == 1]
    raise ValueError('Unknown partition%s: %s' % (plural, ', '.join(sorted(missing))))
249 250


Łukasz Nowak's avatar
Łukasz Nowak committed
251 252 253 254
class Slapgrid(object):
  """ Main class for SlapGrid. Fetches and processes informations from master
  server and pushes usage information to master server.
  """
Antoine Catton's avatar
Antoine Catton committed
255 256 257 258

  class PromiseError(Exception):
    pass

Łukasz Nowak's avatar
Łukasz Nowak committed
259 260 261 262 263 264 265
  def __init__(self,
               software_root,
               instance_root,
               master_url,
               computer_id,
               supervisord_socket,
               supervisord_configuration_path,
266
               buildout,
267
               logger,
268
               maximum_periodicity=86400,
Łukasz Nowak's avatar
Łukasz Nowak committed
269 270
               key_file=None,
               cert_file=None,
271
               signature_private_key_file=None,
Yingjie Xu's avatar
Yingjie Xu committed
272 273 274
               signature_certificate_list=None,
               download_binary_cache_url=None,
               upload_binary_cache_url=None,
275 276
               download_from_binary_cache_url_blacklist=None,
               upload_to_binary_cache_url_blacklist=None,
277
               upload_cache_url=None,
Yingjie Xu's avatar
Yingjie Xu committed
278 279
               download_binary_dir_url=None,
               upload_binary_dir_url=None,
280
               upload_dir_url=None,
Łukasz Nowak's avatar
Łukasz Nowak committed
281 282
               master_ca_file=None,
               certificate_repository_path=None,
283 284 285 286
               promise_timeout=3,
               shacache_cert_file=None,
               shacache_key_file=None,
               shadir_cert_file=None,
287
               shadir_key_file=None,
288
               develop=False,
289
               software_release_filter_list=None,
290 291
               computer_partition_filter_list=None,
               ):
Łukasz Nowak's avatar
Łukasz Nowak committed
292 293 294 295 296 297 298 299 300 301 302 303
    """Makes easy initialisation of class parameters"""
    # Parses arguments
    self.software_root = os.path.abspath(software_root)
    self.instance_root = os.path.abspath(instance_root)
    self.master_url = master_url
    self.computer_id = computer_id
    self.supervisord_socket = supervisord_socket
    self.supervisord_configuration_path = supervisord_configuration_path
    self.key_file = key_file
    self.cert_file = cert_file
    self.master_ca_file = master_ca_file
    self.certificate_repository_path = certificate_repository_path
304
    self.signature_private_key_file = signature_private_key_file
Yingjie Xu's avatar
Yingjie Xu committed
305 306 307
    self.signature_certificate_list = signature_certificate_list
    self.download_binary_cache_url = download_binary_cache_url
    self.upload_binary_cache_url = upload_binary_cache_url
308 309 310 311
    self.download_from_binary_cache_url_blacklist = \
        download_from_binary_cache_url_blacklist
    self.upload_to_binary_cache_url_blacklist = \
        upload_to_binary_cache_url_blacklist
312
    self.upload_cache_url = upload_cache_url
Yingjie Xu's avatar
Yingjie Xu committed
313 314
    self.download_binary_dir_url = download_binary_dir_url
    self.upload_binary_dir_url = upload_binary_dir_url
315
    self.upload_dir_url = upload_dir_url
316 317 318 319
    self.shacache_cert_file = shacache_cert_file
    self.shacache_key_file = shacache_key_file
    self.shadir_cert_file = shadir_cert_file
    self.shadir_key_file = shadir_key_file
320
    self.logger = logger
Łukasz Nowak's avatar
Łukasz Nowak committed
321
    # Creates objects from slap module
Marco Mariani's avatar
Marco Mariani committed
322
    self.slap = slapos.slap.slap()
Łukasz Nowak's avatar
Łukasz Nowak committed
323 324 325 326 327
    self.slap.initializeConnection(self.master_url, key_file=self.key_file,
        cert_file=self.cert_file, master_ca_file=self.master_ca_file)
    self.computer = self.slap.registerComputer(self.computer_id)
    # Defines all needed paths
    self.supervisord_configuration_directory = \
Marco Mariani's avatar
Marco Mariani committed
328
        os.path.join(self.instance_root, 'etc', 'supervisord.conf.d')
329
    self.buildout = buildout
330
    self.promise_timeout = promise_timeout
331
    self.develop = develop
332 333 334

    self.software_release_filter_list = []
    if software_release_filter_list:
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
335 336
      self.software_release_filter_list = \
          software_release_filter_list.split(",")
337

338
    self.computer_partition_filter_list = []
339
    if computer_partition_filter_list:
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
340 341
      self.computer_partition_filter_list = \
          computer_partition_filter_list.split(",")
342

343
    self.maximum_periodicity = maximum_periodicity
Cédric Le Ninivin's avatar
Cédric Le Ninivin committed
344 345

  def getWatchdogLine(self):
346
    invocation_list = [WATCHDOG_PATH]
Cédric Le Ninivin's avatar
Cédric Le Ninivin committed
347
    invocation_list.append("--master-url '%s' " % self.master_url)
348
    if self.certificate_repository_path:
Marco Mariani's avatar
Marco Mariani committed
349 350
      invocation_list.append("--certificate-repository-path '%s'" %
                                self.certificate_repository_path)
Cédric Le Ninivin's avatar
Cédric Le Ninivin committed
351
    invocation_list.append("--computer-id '%s'" % self.computer_id)
352
    invocation_list.append("--instance-root '%s'" % self.instance_root)
Cédric Le Ninivin's avatar
Cédric Le Ninivin committed
353
    return ' '.join(invocation_list)
Łukasz Nowak's avatar
Łukasz Nowak committed
354 355 356 357 358 359 360

  def checkEnvironmentAndCreateStructure(self):
    """Checks for software_root and instance_root existence, then creates
       needed files and directories.
    """
    # Checks for software_root and instance_root existence
    if not os.path.isdir(self.software_root):
361
      raise OSError('%s does not exist.' % self.software_root)
Łukasz Nowak's avatar
Łukasz Nowak committed
362
    if not os.path.isdir(self.instance_root):
363
      raise OSError('%s does not exist.' % self.instance_root)
Łukasz Nowak's avatar
Łukasz Nowak committed
364
    # Creates everything needed
Marco Mariani's avatar
Marco Mariani committed
365

366 367 368 369 370 371 372 373 374 375
    # Create directory accessible for the instances.
    var_directory = os.path.join(self.instance_root, 'var')
    if not os.path.isdir(var_directory):
      os.mkdir(var_directory)

    os.chmod(var_directory, stat.S_IRWXU | stat.S_IROTH | stat.S_IXOTH | \
                            stat.S_IRGRP | stat.S_IXGRP )

    mkdir_p(os.path.join(self.instance_root, 'var'), 0o755)

376 377 378
    # Creates instance_root structure
    createPrivateDirectory(os.path.join(self.instance_root, 'var', 'log'))
    createPrivateDirectory(os.path.join(self.instance_root, 'var', 'run'))
Marco Mariani's avatar
Marco Mariani committed
379 380

    createPrivateDirectory(os.path.join(self.instance_root, 'etc'))
381
    createPrivateDirectory(self.supervisord_configuration_directory)
382

383 384 385 386 387 388 389 390 391 392 393 394 395
    # Creates supervisord configuration
    updateFile(self.supervisord_configuration_path,
      pkg_resources.resource_stream(__name__,
        'templates/supervisord.conf.in').read() % {
            'supervisord_configuration_directory': self.supervisord_configuration_directory,
            'supervisord_socket': os.path.abspath(self.supervisord_socket),
            'supervisord_loglevel': 'info',
            'supervisord_logfile': os.path.abspath(os.path.join(self.instance_root, 'var', 'log', 'supervisord.log')),
            'supervisord_logfile_maxbytes': '50MB',
            'supervisord_nodaemon': 'false',
            'supervisord_pidfile': os.path.abspath(os.path.join(self.instance_root, 'var', 'run', 'supervisord.pid')),
            'supervisord_logfile_backups': '10',
            'watchdog_command': self.getWatchdogLine(),
Marco Mariani's avatar
Marco Mariani committed
396 397
        }
    )
Łukasz Nowak's avatar
Łukasz Nowak committed
398 399 400

  def getComputerPartitionList(self):
    try:
401
      return self.computer.getComputerPartitionList()
Marco Mariani's avatar
Marco Mariani committed
402 403
    except socket.error as exc:
      self.logger.fatal(exc)
404
      raise
Łukasz Nowak's avatar
Łukasz Nowak committed
405 406 407 408 409

  def processSoftwareReleaseList(self):
    """Will process each Software Release.
    """
    self.checkEnvironmentAndCreateStructure()
410
    self.logger.info('Processing software releases...')
411
    # Boolean to know if every instance has correctly been deployed
Łukasz Nowak's avatar
Łukasz Nowak committed
412 413
    clean_run = True
    for software_release in self.computer.getSoftwareReleaseList():
Łukasz Nowak's avatar
Łukasz Nowak committed
414
      state = software_release.getState()
Łukasz Nowak's avatar
Łukasz Nowak committed
415 416
      try:
        software_release_uri = software_release.getURI()
Marco Mariani's avatar
Marco Mariani committed
417
        url_hash = md5digest(software_release_uri)
418
        software_path = os.path.join(self.software_root, url_hash)
Łukasz Nowak's avatar
Łukasz Nowak committed
419 420
        software = Software(url=software_release_uri,
            software_root=self.software_root,
421
            buildout=self.buildout,
422
            logger=self.logger,
423
            signature_private_key_file=self.signature_private_key_file,
Yingjie Xu's avatar
Yingjie Xu committed
424 425 426
            signature_certificate_list=self.signature_certificate_list,
            download_binary_cache_url=self.download_binary_cache_url,
            upload_binary_cache_url=self.upload_binary_cache_url,
Marco Mariani's avatar
Marco Mariani committed
427
            download_from_binary_cache_url_blacklist=
428
                self.download_from_binary_cache_url_blacklist,
Marco Mariani's avatar
Marco Mariani committed
429
            upload_to_binary_cache_url_blacklist=
430
                self.upload_to_binary_cache_url_blacklist,
431
            upload_cache_url=self.upload_cache_url,
Yingjie Xu's avatar
Yingjie Xu committed
432 433
            download_binary_dir_url=self.download_binary_dir_url,
            upload_binary_dir_url=self.upload_binary_dir_url,
434 435 436 437
            upload_dir_url=self.upload_dir_url,
            shacache_cert_file=self.shacache_cert_file,
            shacache_key_file=self.shacache_key_file,
            shadir_cert_file=self.shadir_cert_file,
Łukasz Nowak's avatar
Łukasz Nowak committed
438 439
            shadir_key_file=self.shadir_key_file)
        if state == 'available':
440
          completed_tag = os.path.join(software_path, '.completed')
Marco Mariani's avatar
Marco Mariani committed
441 442 443 444
          if (self.develop or (not os.path.exists(completed_tag) and
                 len(self.software_release_filter_list) == 0) or
                 url_hash in self.software_release_filter_list or
                 url_hash in (md5digest(uri) for uri in self.software_release_filter_list)):
445 446 447 448 449
            try:
              software_release.building()
            except NotFoundError:
              pass
            software.install()
Marco Mariani's avatar
Marco Mariani committed
450 451
            with open(completed_tag, 'w') as fout:
              fout.write(time.asctime())
Łukasz Nowak's avatar
Łukasz Nowak committed
452
        elif state == 'destroyed':
453
          if os.path.exists(software_path):
454
            self.logger.info('Destroying %r...' % software_release_uri)
455
            software.destroy()
456
            self.logger.info('Destroyed %r.' % software_release_uri)
457
      # Send log before exiting
Łukasz Nowak's avatar
Łukasz Nowak committed
458
      except (SystemExit, KeyboardInterrupt):
459
        software_release.error(traceback.format_exc(), logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
460
        raise
461 462

      # Buildout failed: send log but don't print it to output (already done)
Marco Mariani's avatar
Marco Mariani committed
463
      except BuildoutFailedError as exc:
464 465
        clean_run = False
        try:
466
          software_release.error(exc, logger=self.logger)
467 468 469
        except (SystemExit, KeyboardInterrupt):
          raise
        except Exception:
470
          self.logger.exception('Problem while reporting error, continuing:')
471 472

      # For everything else: log it, send it, continue.
Łukasz Nowak's avatar
Łukasz Nowak committed
473
      except Exception:
474 475
        self.logger.exception('')
        software_release.error(traceback.format_exc(), logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
476 477
        clean_run = False
      else:
Łukasz Nowak's avatar
Łukasz Nowak committed
478
        if state == 'available':
479 480
          try:
            software_release.available()
481
          except (NotFoundError, ServerError):
482
            pass
Łukasz Nowak's avatar
Łukasz Nowak committed
483
        elif state == 'destroyed':
484 485
          try:
            software_release.destroyed()
486
          except (NotFoundError, ServerError):
487
            self.logger.exception('')
488
    self.logger.info('Finished software releases.')
489 490 491 492 493

    # Return success value
    if not clean_run:
      return SLAPGRID_FAIL
    return SLAPGRID_SUCCESS
Łukasz Nowak's avatar
Łukasz Nowak committed
494 495 496

  def _launchSupervisord(self):
    launchSupervisord(self.supervisord_socket,
Marco Mariani's avatar
Marco Mariani committed
497 498
                      self.supervisord_configuration_path,
                      logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
499

Antoine Catton's avatar
Antoine Catton committed
500
  def _checkPromises(self, computer_partition):
501
    self.logger.info("Checking promises...")
Marco Mariani's avatar
Marco Mariani committed
502
    instance_path = os.path.join(self.instance_root, computer_partition.getId())
Antoine Catton's avatar
Antoine Catton committed
503 504 505 506 507 508 509 510

    uid, gid = None, None
    stat_info = os.stat(instance_path)

    #stat sys call to get statistics informations
    uid = stat_info.st_uid
    gid = stat_info.st_gid

511
    promise_present = False
Antoine Catton's avatar
Antoine Catton committed
512 513 514 515
    # Get the list of promises
    promise_dir = os.path.join(instance_path, 'etc', 'promise')
    if os.path.exists(promise_dir) and os.path.isdir(promise_dir):
      # Check whether every promise is kept
Marco Mariani's avatar
Marco Mariani committed
516
      for promise in os.listdir(promise_dir):
517
        promise_present = True
Antoine Catton's avatar
Antoine Catton committed
518

Antoine Catton's avatar
Antoine Catton committed
519 520 521
        command = [os.path.join(promise_dir, promise)]

        promise = os.path.basename(command[0])
522
        self.logger.info("Checking promise '%s'.", promise)
Antoine Catton's avatar
Antoine Catton committed
523

524
        process_handler = subprocess.Popen(command,
525
                                           preexec_fn=lambda: dropPrivileges(uid, gid, logger=self.logger),
Marco Mariani's avatar
Marco Mariani committed
526
                                           cwd=instance_path,
Marco Mariani's avatar
Marco Mariani committed
527
                                           env=None if sys.platform == 'cygwin' else {},
Marco Mariani's avatar
Marco Mariani committed
528 529 530
                                           stdout=subprocess.PIPE,
                                           stderr=subprocess.PIPE,
                                           stdin=subprocess.PIPE)
531 532 533
        process_handler.stdin.flush()
        process_handler.stdin.close()
        process_handler.stdin = None
Antoine Catton's avatar
Antoine Catton committed
534 535 536 537

        time.sleep(self.promise_timeout)

        if process_handler.poll() is None:
538
          process_handler.terminate()
539
          raise Slapgrid.PromiseError("The promise '%s' timed out" % promise)
Antoine Catton's avatar
Antoine Catton committed
540 541 542
        elif process_handler.poll() != 0:
          stderr = process_handler.communicate()[1]
          if stderr is None:
543
            stderr = "No error output from '%s'." % promise
Antoine Catton's avatar
Antoine Catton committed
544
          else:
545
            stderr = "Promise '%s':" % promise + stderr
Antoine Catton's avatar
Antoine Catton committed
546 547
          raise Slapgrid.PromiseError(stderr)

Antoine Catton's avatar
Antoine Catton committed
548 549 550
    if not promise_present:
      self.logger.info("No promise.")

551 552 553 554 555 556 557 558 559 560 561 562 563
  def processComputerPartition(self, computer_partition):
    """
    Process a Computer Partition, depending on its state
    """
    computer_partition_id = computer_partition.getId()

    # Sanity checks before processing
    # Those values should not be None or empty string or any falsy value
    if not computer_partition_id:
      raise ValueError('Computer Partition id is empty.')

    # Check if we defined explicit list of partitions to process.
    # If so, if current partition not in this list, skip.
564
    if self.computer_partition_filter_list and \
565 566 567
         (computer_partition_id not in self.computer_partition_filter_list):
      return

568
    self.logger.debug('Check if %s requires processing...' % computer_partition_id)
569

570 571 572
    instance_path = os.path.join(self.instance_root, computer_partition_id)

    # Try to get partition timestamp (last modification date)
573 574 575 576
    timestamp_path = os.path.join(
        instance_path,
        COMPUTER_PARTITION_TIMESTAMP_FILENAME
    )
577 578 579 580 581 582
    parameter_dict = computer_partition.getInstanceParameterDict()
    if 'timestamp' in parameter_dict:
      timestamp = parameter_dict['timestamp']
    else:
      timestamp = None

583 584 585 586 587 588 589
    try:
      software_url = computer_partition.getSoftwareRelease().getURI()
    except NotFoundError:
      # Problem with instance: SR URI not set.
      # Try to process it anyway, it may need to be deleted.
      software_url = None
    try:
Marco Mariani's avatar
Marco Mariani committed
590
      software_path = os.path.join(self.software_root, md5digest(software_url))
591 592 593 594 595
    except TypeError:
      # Problem with instance: SR URI not set.
      # Try to process it anyway, it may need to be deleted.
      software_path = None

596
    periodicity = self.maximum_periodicity
597
    if software_path:
598 599 600 601 602 603 604
      periodicity_path = os.path.join(software_path, 'periodicity')
      if os.path.exists(periodicity_path):
        try:
          periodicity = int(open(periodicity_path).read())
        except ValueError:
          os.remove(periodicity_path)
          self.logger.exception('')
605 606 607 608

    # Check if timestamp from server is more recent than local one.
    # If not: it's not worth processing this partition (nothing has
    # changed).
Marco Mariani's avatar
Marco Mariani committed
609 610
    if (computer_partition_id not in self.computer_partition_filter_list and
          not self.develop and os.path.exists(timestamp_path)):
611 612 613 614
      old_timestamp = open(timestamp_path).read()
      last_runtime = int(os.path.getmtime(timestamp_path))
      if timestamp:
        try:
615 616 617
          if periodicity == 0:
            os.remove(timestamp_path)
          elif int(timestamp) <= int(old_timestamp):
618 619
            # Check periodicity, i.e if periodicity is one day, partition
            # should be processed at least every day.
620
            if int(time.time()) <= (last_runtime + periodicity) or periodicity < 0:
621
              self.logger.debug('Partition already up-to-date, skipping.')
622
              return
623 624 625 626
            else:
              # Periodicity forced processing this partition. Removing
              # the timestamp file in case it fails.
              os.remove(timestamp_path)
627 628
        except ValueError:
          os.remove(timestamp_path)
629
          self.logger.exception('')
630

631 632 633 634 635 636 637
    # Include Partition Logging
    log_folder_path = "%s/.slapgrid/log" % instance_path
    mkdir_p(log_folder_path)
    partition_file_handler = logging.FileHandler(
                filename="%s/instance.log" % (log_folder_path)
            )
    stat_info = os.stat(instance_path)
638
    chownDirectory("%s/.slapgrid" % instance_path,
639 640 641 642 643 644 645 646 647 648 649 650 651
                   uid=stat_info.st_uid,
                   gid=stat_info.st_gid)

    formatter = logging.Formatter(
       '[%(asctime)s] %(levelname)-8s %(name)s %(message)s')
    partition_file_handler.setFormatter(formatter)
    self.logger.addHandler(partition_file_handler)

    try:
      self.logger.info('Processing Computer Partition %s.' % computer_partition_id)
      self.logger.info('  Software URL: %s' % software_url)
      self.logger.info('  Software path: %s' % software_path)
      self.logger.info('  Instance path: %s' % instance_path)
652

653 654 655 656 657 658
      filter_dict = getattr(computer_partition, '_filter_dict', None)
      if filter_dict:
        retention_delay = filter_dict.get('retention_delay', '0')
      else:
        retention_delay = '0'

659 660 661 662 663 664 665 666 667 668 669 670 671 672
      local_partition = Partition(
        software_path=software_path,
        instance_path=instance_path,
        supervisord_partition_configuration_path=os.path.join(
          self.supervisord_configuration_directory, '%s.conf' %
          computer_partition_id),
        supervisord_socket=self.supervisord_socket,
        computer_partition=computer_partition,
        computer_id=self.computer_id,
        partition_id=computer_partition_id,
        server_url=self.master_url,
        software_release_url=software_url,
        certificate_repository_path=self.certificate_repository_path,
        buildout=self.buildout,
673
        logger=self.logger,
674 675
        retention_delay=retention_delay,
      )
676
      computer_partition_state = computer_partition.getState()
677

678 679 680
      # XXX this line breaks 37 tests
      # self.logger.info('  Instance type: %s' % computer_partition.getType())
      self.logger.info('  Instance status: %s' % computer_partition_state)
681

682
      if computer_partition_state == COMPUTER_PARTITION_STARTED_STATE:
683 684
        local_partition.install()
        computer_partition.available()
685 686 687 688 689 690 691 692 693 694 695 696
        local_partition.start()
        self._checkPromises(computer_partition)
        computer_partition.started()
      elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE:
        try:
          # We want to process the partition, even if stopped, because it should
          # propagate the state to children if any.
          local_partition.install()
          computer_partition.available()
        finally:
          # Instance has to be stopped even if buildout/reporting is wrong.
          local_partition.stop()
697
        computer_partition.stopped()
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
      elif computer_partition_state == COMPUTER_PARTITION_DESTROYED_STATE:
        local_partition.stop()
        try:
          computer_partition.stopped()
        except (SystemExit, KeyboardInterrupt):
          computer_partition.error(traceback.format_exc(), logger=self.logger)
          raise
        except Exception:
          pass
      else:
        error_string = "Computer Partition %r has unsupported state: %s" % \
          (computer_partition_id, computer_partition_state)
        computer_partition.error(error_string, logger=self.logger)
        raise NotImplementedError(error_string)
    finally:
       self.logger.removeHandler(partition_file_handler)
714 715 716 717 718

    # If partition has been successfully processed, write timestamp
    if timestamp:
      open(timestamp_path, 'w').write(timestamp)

719
  def FilterComputerPartitionList(self, computer_partition_list):
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
720
    """
721
    Try to filter valid partitions to be processed from free partitions.
Łukasz Nowak's avatar
Łukasz Nowak committed
722
    """
723 724
    filtered_computer_partition_list = []
    for computer_partition in computer_partition_list:
Łukasz Nowak's avatar
Łukasz Nowak committed
725
      try:
726 727 728 729 730 731 732
        computer_partition_path = os.path.join(self.instance_root,
            computer_partition.getId())
        if not os.path.exists(computer_partition_path):
          raise NotFoundError('Partition directory %s does not exist.' %
              computer_partition_path)
        # Check state of partition. If it is in "destroyed" state, check if it
        # partition is actually installed in the Computer or if it is "free"
733
        # partition, and check if it has some Software information.
734 735 736
        # XXX-Cedric: Temporary AND ugly solution to check if an instance
        # is in the partition. Dangerous because not 100% sure it is empty
        computer_partition_state = computer_partition.getState()
737 738 739 740
        try:
          software_url = computer_partition.getSoftwareRelease().getURI()
        except (NotFoundError, TypeError, NameError):
          software_url = None
741
        if computer_partition_state == COMPUTER_PARTITION_DESTROYED_STATE and \
742 743
           os.listdir(computer_partition_path) == [] and \
           not software_url:
744
          continue
745

746 747 748 749 750 751 752
        # Everything seems fine
        filtered_computer_partition_list.append(computer_partition)

      # XXX-Cedric: factor all this error handling

      # Send log before exiting
      except (SystemExit, KeyboardInterrupt):
753
        computer_partition.error(traceback.format_exc(), logger=self.logger)
754 755
        raise

Marco Mariani's avatar
Marco Mariani committed
756
      except Exception as exc:
757 758 759 760
        # if Buildout failed: send log but don't print it to output (already done)
        if not isinstance(exc, BuildoutFailedError):
          # For everything else: log it, send it, continue.
          self.logger.exception('')
761
        try:
762
          computer_partition.error(exc, logger=self.logger)
763 764 765
        except (SystemExit, KeyboardInterrupt):
          raise
        except Exception:
766
          self.logger.exception('Problem while reporting error, continuing:')
767 768 769 770 771 772 773

    return filtered_computer_partition_list

  def processComputerPartitionList(self):
    """
    Will start supervisord and process each Computer Partition.
    """
774
    self.logger.info('Processing computer partitions...')
775 776 777
    # Prepares environment
    self.checkEnvironmentAndCreateStructure()
    self._launchSupervisord()
778 779

    # Boolean to know if every instance has correctly been deployed
780
    clean_run = True
781 782
    # Boolean to know if every promises correctly passed
    clean_run_promise = True
783

784 785 786
    check_required_only_partitions([cp.getId() for cp in self.getComputerPartitionList()],
                                   self.computer_partition_filter_list)

787 788 789 790 791 792 793 794 795
    # Filter all dummy / empty partitions
    computer_partition_list = self.FilterComputerPartitionList(
        self.getComputerPartitionList())

    for computer_partition in computer_partition_list:
      # Nothing should raise outside of the current loop iteration, so that
      # even if something is terribly wrong while processing an instance, it
      # won't prevent processing other ones.
      try:
796
        # Process the partition itself
797
        self.processComputerPartition(computer_partition)
798

799
      # Send log before exiting
Łukasz Nowak's avatar
Łukasz Nowak committed
800
      except (SystemExit, KeyboardInterrupt):
801
        computer_partition.error(traceback.format_exc(), logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
802
        raise
803

Marco Mariani's avatar
Marco Mariani committed
804
      except Slapgrid.PromiseError as exc:
805 806
        clean_run_promise = False
        try:
807
          self.logger.error(exc)
808
          computer_partition.error(exc, logger=self.logger)
809 810 811
        except (SystemExit, KeyboardInterrupt):
          raise
        except Exception:
812
          self.logger.exception('Problem while reporting error, continuing:')
813

Marco Mariani's avatar
Marco Mariani committed
814
      except Exception as exc:
Łukasz Nowak's avatar
Łukasz Nowak committed
815
        clean_run = False
816 817 818 819
        # if Buildout failed: send log but don't print it to output (already done)
        if not isinstance(exc, BuildoutFailedError):
          # For everything else: log it, send it, continue.
          self.logger.exception('')
820
        try:
821
          computer_partition.error(exc, logger=self.logger)
822 823 824
        except (SystemExit, KeyboardInterrupt):
          raise
        except Exception:
825
          self.logger.exception('Problem while reporting error, continuing:')
826

827
    self.logger.info('Finished computer partitions.')
828 829 830 831 832 833 834

    # Return success value
    if not clean_run:
      return SLAPGRID_FAIL
    if not clean_run_promise:
      return SLAPGRID_PROMISE_FAIL
    return SLAPGRID_SUCCESS
Łukasz Nowak's avatar
Łukasz Nowak committed
835

836 837 838 839 840
  def validateXML(self, to_be_validated, xsd_model):
    """Validates a given xml file"""
    #We retrieve the xsd model
    xsd_model = StringIO.StringIO(xsd_model)
    xmlschema_doc = etree.parse(xsd_model)
Łukasz Nowak's avatar
Łukasz Nowak committed
841 842
    xmlschema = etree.XMLSchema(xmlschema_doc)

843
    try:
844
      document = etree.fromstring(to_be_validated)
Marco Mariani's avatar
Marco Mariani committed
845
    except (etree.XMLSyntaxError, etree.DocumentInvalid) as exc:
Marco Mariani's avatar
Marco Mariani committed
846 847
      self.logger.info('Failed to parse this XML report :  %s\n%s' %
                          (to_be_validated, _formatXMLError(exc)))
848
      self.logger.error(_formatXMLError(exc))
849 850
      return False

Łukasz Nowak's avatar
Łukasz Nowak committed
851 852 853 854 855
    if xmlschema.validate(document):
      return True

    return False

856 857 858
  def asXML(self, computer_partition_usage_list):
    """Generates a XML report from computer partition usage list
    """
859 860 861 862 863 864 865 866 867 868 869 870 871 872
    xml = ['<?xml version="1.0"?>',
           '<journal>',
           '<transaction type="Sale Packing List">',
           '<title>Resource consumptions</title>',
           '<start_date></start_date>',
           '<stop_date>%s</stop_date>' % time.strftime("%Y-%m-%d at %H:%M:%S"),
           '<reference>%s</reference>' % self.computer_id,
           '<currency></currency>',
           '<payment_mode></payment_mode>',
           '<category></category>',
           '<arrow type="Administration">',
           '<source></source>',
           '<destination></destination>',
           '</arrow>']
873 874 875

    for computer_partition_usage in computer_partition_usage_list:
      try:
876
        root = etree.fromstring(computer_partition_usage.usage)
Marco Mariani's avatar
Marco Mariani committed
877
      except UnicodeError as exc:
878
        self.logger.info("Failed to read %s." % computer_partition_usage.usage)
879
        self.logger.error(UnicodeError)
Marco Mariani's avatar
Marco Mariani committed
880 881
        raise UnicodeError("Failed to read %s: %s" % (computer_partition_usage.usage, exc))
      except (etree.XMLSyntaxError, etree.DocumentInvalid) as exc:
882
        self.logger.info("Failed to parse %s." % computer_partition_usage.usage)
Marco Mariani's avatar
Marco Mariani committed
883 884 885 886
        self.logger.error(exc)
        raise _formatXMLError(exc)
      except Exception as exc:
        raise Exception("Failed to generate XML report: %s" % exc)
887 888

      for movement in root.findall('movement'):
889 890 891 892
        xml.append('<movement>')
        for child in movement.getchildren():
          if child.tag == "reference":
            xml.append('<%s>%s</%s>' % (child.tag, computer_partition_usage.getId(), child.tag))
893
          else:
894 895
            xml.append('<%s>%s</%s>' % (child.tag, child.text, child.tag))
        xml.append('</movement>')
896

897
    xml.append('</transaction></journal>')
898

899
    return ''.join(xml)
900

Łukasz Nowak's avatar
Łukasz Nowak committed
901 902 903
  def agregateAndSendUsage(self):
    """Will agregate usage from each Computer Partition.
    """
904 905 906 907
    # Prepares environment
    self.checkEnvironmentAndCreateStructure()
    self._launchSupervisord()

Łukasz Nowak's avatar
Łukasz Nowak committed
908 909
    slap_computer_usage = self.slap.registerComputer(self.computer_id)
    computer_partition_usage_list = []
910
    self.logger.info('Aggregating and sending usage reports...')
Łukasz Nowak's avatar
Łukasz Nowak committed
911

912 913 914 915 916 917 918 919 920
    #We retrieve XSD models
    try:
      computer_consumption_model = \
        pkg_resources.resource_string(
          'slapos.slap',
          'doc/computer_consumption.xsd')
    except IOError:
      computer_consumption_model = \
        pkg_resources.resource_string(
921
          __name__,
922 923 924 925 926 927 928 929 930 931
          '../../../../slapos/slap/doc/computer_consumption.xsd')

    try:
      partition_consumption_model = \
        pkg_resources.resource_string(
          'slapos.slap',
          'doc/partition_consumption.xsd')
    except IOError:
      partition_consumption_model = \
        pkg_resources.resource_string(
932
          __name__,
933 934
          '../../../../slapos/slap/doc/partition_consumption.xsd')

Łukasz Nowak's avatar
Łukasz Nowak committed
935
    clean_run = True
936
    # Loop over the different computer partitions
937 938
    computer_partition_list = self.FilterComputerPartitionList(
       slap_computer_usage.getComputerPartitionList())
939

940
    for computer_partition in computer_partition_list:
941 942
      try:
        computer_partition_id = computer_partition.getId()
943

944
        # We want to execute all the script in the report folder
945 946 947 948 949 950 951
        instance_path = os.path.join(self.instance_root,
            computer_partition.getId())
        report_path = os.path.join(instance_path, 'etc', 'report')
        if os.path.isdir(report_path):
          script_list_to_run = os.listdir(report_path)
        else:
          script_list_to_run = []
Marco Mariani's avatar
Marco Mariani committed
952

953
        # We now generate the pseudorandom name for the xml file
954 955 956 957 958
        # and we add it in the invocation_list
        f = tempfile.NamedTemporaryFile()
        name_xml = '%s.%s' % ('slapreport', os.path.basename(f.name))
        path_to_slapreport = os.path.join(instance_path, 'var', 'xml_report',
            name_xml)
Marco Mariani's avatar
Marco Mariani committed
959

960 961 962 963 964
        failed_script_list = []
        for script in script_list_to_run:
          invocation_list = []
          invocation_list.append(os.path.join(instance_path, 'etc', 'report',
            script))
965
          # We add the xml_file name to the invocation_list
966 967 968
          #f = tempfile.NamedTemporaryFile()
          #name_xml = '%s.%s' % ('slapreport', os.path.basename(f.name))
          #path_to_slapreport = os.path.join(instance_path, 'var', name_xml)
Marco Mariani's avatar
Marco Mariani committed
969

970
          invocation_list.append(path_to_slapreport)
971
          # Dropping privileges
972 973 974 975 976 977
          uid, gid = None, None
          stat_info = os.stat(instance_path)
          #stat sys call to get statistics informations
          uid = stat_info.st_uid
          gid = stat_info.st_gid
          process_handler = SlapPopen(invocation_list,
978
                                      preexec_fn=lambda: dropPrivileges(uid, gid, logger=self.logger),
Marco Mariani's avatar
Marco Mariani committed
979 980
                                      cwd=os.path.join(instance_path, 'etc', 'report'),
                                      env=None,
Marco Mariani's avatar
Marco Mariani committed
981
                                      stdout=subprocess.PIPE,
982 983
                                      stderr=subprocess.STDOUT,
                                      logger=self.logger)
984 985 986 987
          if process_handler.returncode is None:
            process_handler.kill()
          if process_handler.returncode != 0:
            clean_run = False
988
            failed_script_list.append("Script %r failed." % script)
989
            self.logger.warning('Failed to run %r' % invocation_list)
990
          if failed_script_list:
991
            computer_partition.error('\n'.join(failed_script_list), logger=self.logger)
992 993
      # Whatever happens, don't stop processing other instances
      except Exception:
994 995
        self.logger.exception('Cannot run usage script(s) for %r:' %
                                  computer_partition.getId())
Łukasz Nowak's avatar
Łukasz Nowak committed
996

997
    # Now we loop through the different computer partitions to report
Łukasz Nowak's avatar
Łukasz Nowak committed
998
    report_usage_issue_cp_list = []
999
    for computer_partition in computer_partition_list:
1000 1001 1002 1003
      try:
        filename_delete_list = []
        computer_partition_id = computer_partition.getId()
        instance_path = os.path.join(self.instance_root, computer_partition_id)
1004
        dir_report_list = [os.path.join(instance_path, 'var', 'xml_report'),
1005
            os.path.join(self.instance_root, 'var', 'xml_report',
1006
                         computer_partition_id)]
1007

1008 1009 1010 1011 1012
        for dir_reports in dir_report_list:
          # The directory xml_report contain a number of files equal
          # to the number of software instance running inside the same partition
          if os.path.isdir(dir_reports):
            filename_list = os.listdir(dir_reports)
1013
          else:
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
            filename_list = []
          # self.logger.debug('name List %s' % filename_list)

          for filename in filename_list:

            file_path = os.path.join(dir_reports, filename)
            if os.path.exists(file_path):
              usage = open(file_path, 'r').read()

              # We check the validity of xml content of each reports
              if not self.validateXML(usage, partition_consumption_model):
                self.logger.info('WARNING: The XML file %s generated by slapreport is '
                                 'not valid - This report is left as is at %s where you can '
                                 'inspect what went wrong ' % (filename, dir_reports))
                # Warn the SlapOS Master that a partition generates corrupted xml
                # report
              else:
                computer_partition_usage = self.slap.registerComputerPartition(
                    self.computer_id, computer_partition_id)
                computer_partition_usage.setUsage(usage)
                computer_partition_usage_list.append(computer_partition_usage)
                filename_delete_list.append(filename)
            else:
              self.logger.debug('Usage report %r not found, ignored' % file_path)
Łukasz Nowak's avatar
Łukasz Nowak committed
1038

1039 1040 1041
          # After sending the aggregated file we remove all the valid xml reports
          for filename in filename_delete_list:
            os.remove(os.path.join(dir_reports, filename))
1042 1043 1044

      # Whatever happens, don't stop processing other instances
      except Exception:
1045 1046
        self.logger.exception('Cannot run usage script(s) for %r:' %
                                computer_partition.getId())
1047 1048

    for computer_partition_usage in computer_partition_usage_list:
Marco Mariani's avatar
Marco Mariani committed
1049 1050
      self.logger.info('computer_partition_usage_list: %s - %s' %
                       (computer_partition_usage.usage, computer_partition_usage.getId()))
1051

1052
    # If there is, at least, one report
1053
    if computer_partition_usage_list != []:
Łukasz Nowak's avatar
Łukasz Nowak committed
1054
      try:
1055
        # We generate the final XML report with asXML method
1056 1057
        computer_consumption = self.asXML(computer_partition_usage_list)

1058
        self.logger.info('Final xml report: %s' % computer_consumption)
1059

1060
        # We test the XML report before sending it
1061
        if self.validateXML(computer_consumption, computer_consumption_model):
1062
          self.logger.info('XML file generated by asXML is valid')
1063 1064
          slap_computer_usage.reportUsage(computer_consumption)
        else:
1065
          self.logger.info('XML file generated by asXML is not valid !')
1066
          raise ValueError('XML file generated by asXML is not valid !')
Łukasz Nowak's avatar
Łukasz Nowak committed
1067
      except Exception:
1068
        issue = "Cannot report usage for %r: %s" % (
Marco Mariani's avatar
Marco Mariani committed
1069 1070
            computer_partition.getId(),
            traceback.format_exc())
1071
        self.logger.info(issue)
1072
        computer_partition.error(issue, logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
1073 1074
        report_usage_issue_cp_list.append(computer_partition_id)

1075
    for computer_partition in computer_partition_list:
1076
      if computer_partition.getState() == COMPUTER_PARTITION_DESTROYED_STATE:
Łukasz Nowak's avatar
Łukasz Nowak committed
1077
        try:
1078
          computer_partition_id = computer_partition.getId()
1079
          try:
1080 1081
            software_url = computer_partition.getSoftwareRelease().getURI()
            software_path = os.path.join(self.software_root, md5digest(software_url))
1082 1083 1084
          except (NotFoundError, TypeError):
            software_url = None
            software_path = None
1085

1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
          local_partition = Partition(
            software_path=software_path,
            instance_path=os.path.join(self.instance_root,
                computer_partition.getId()),
            supervisord_partition_configuration_path=os.path.join(
              self.supervisord_configuration_directory, '%s.conf' %
              computer_partition_id),
            supervisord_socket=self.supervisord_socket,
            computer_partition=computer_partition,
            computer_id=self.computer_id,
            partition_id=computer_partition_id,
            server_url=self.master_url,
            software_release_url=software_url,
            certificate_repository_path=self.certificate_repository_path,
1100
            buildout=self.buildout,
1101
            logger=self.logger,
1102
          )
Łukasz Nowak's avatar
Łukasz Nowak committed
1103 1104 1105 1106
          local_partition.stop()
          try:
            computer_partition.stopped()
          except (SystemExit, KeyboardInterrupt):
1107
            computer_partition.error(traceback.format_exc(), logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
1108 1109 1110
            raise
          except Exception:
            pass
1111
          if computer_partition.getId() in report_usage_issue_cp_list:
1112 1113
            self.logger.info('Ignoring destruction of %r, as no report usage was sent' %
                                computer_partition.getId())
1114
            continue
1115
          destroyed = local_partition.destroy()
Łukasz Nowak's avatar
Łukasz Nowak committed
1116
        except (SystemExit, KeyboardInterrupt):
1117
          computer_partition.error(traceback.format_exc(), logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
1118 1119 1120
          raise
        except Exception:
          clean_run = False
1121
          self.logger.exception('')
Marco Mariani's avatar
Marco Mariani committed
1122
          exc = traceback.format_exc()
1123
          computer_partition.error(exc, logger=self.logger)
Łukasz Nowak's avatar
Łukasz Nowak committed
1124
        try:
1125 1126
          if destroyed:
            computer_partition.destroyed()
Marco Mariani's avatar
Marco Mariani committed
1127
        except NotFoundError:
1128 1129 1130
          self.logger.debug('Ignored slap error while trying to inform about '
                            'destroying not fully configured Computer Partition %r' %
                                computer_partition.getId())
1131
        except ServerError as server_error:
1132 1133 1134
          self.logger.debug('Ignored server error while trying to inform about '
                            'destroying Computer Partition %r. Error is:\n%r' %
                                (computer_partition.getId(), server_error.args[0]))
Łukasz Nowak's avatar
Łukasz Nowak committed
1135

1136
    self.logger.info('Finished usage reports.')
1137 1138 1139 1140 1141

    # Return success value
    if not clean_run:
      return SLAPGRID_FAIL
    return SLAPGRID_SUCCESS