Beispiel #1
0
 def create_image(self, **kwargs):
     if self.times_to_fail is None:
         raise exceptions.BuilderError("Test fail image upload.")
     self.times_failed += 1
     if self.times_failed <= self.times_to_fail:
         raise exceptions.BuilderError("Test fail image upload.")
     else:
         return super(FakeUploadFailCloud, self).create_image(**kwargs)
Beispiel #2
0
    def uploadImage(self, image_id, provider_name, image_name):
        start_time = time.time()
        timestamp = int(start_time)

        provider = self._config.providers[provider_name]
        image_type = provider.image_type

        image_files = DibImageFile.from_image_id(self._config.imagesdir,
                                                 image_id)
        for f in image_files:
            self.log.debug("Found image file of type %s for image id: %s" %
                           (f.extension, f.image_id))
        image_files = filter(lambda x: x.extension == image_type, image_files)
        if len(image_files) == 0:
            raise exceptions.BuilderInvalidCommandError(
                "Unable to find image file of type %s for id %s to upload" %
                (image_type, image_id)
            )
        if len(image_files) > 1:
            raise exceptions.BuilderError(
                "Found more than one image for id %s. This should never "
                "happen.", image_id
            )

        image_file = image_files[0]
        filename = image_file.to_path(self._config.imagesdir,
                                      with_extension=True)

        dummy_image = type('obj', (object,),
                           {'name': image_name})
        ext_image_name = provider.template_hostname.format(
            provider=provider, image=dummy_image, timestamp=str(timestamp))
        self.log.info("Uploading dib image id: %s from %s in %s" %
                      (image_id, filename, provider.name))

        manager = self._config.provider_managers[provider.name]
        try:
            provider_image = provider.images[image_name]
        except KeyError:
            raise exceptions.BuilderInvalidCommandError(
                "Could not find matching provider image for %s", image_name
            )
        image_meta = provider_image.meta
        # uploadImage is synchronous
        external_id = manager.uploadImage(
            ext_image_name, filename,
            image_type=image_file.extension,
            meta=image_meta)

        if self.statsd:
            dt = int((time.time() - start_time) * 1000)
            key = 'nodepool.image_update.%s.%s' % (image_name,
                                                   provider.name)
            self.statsd.timing(key, dt)
            self.statsd.incr(key)

        self.log.info("Image id: %s in %s is ready" % (image_id,
                                                       provider.name))
        return external_id
Beispiel #3
0
 def to_path(self, images_dir, with_extension=True):
     my_path = os.path.join(images_dir, self.image_id)
     if with_extension:
         if self.extension is None:
             raise exceptions.BuilderError(
                 'Cannot specify image extension of None')
         my_path += '.' + self.extension
     return my_path
Beispiel #4
0
    def start(self):
        '''
        Start the builder.

        The builder functionality is encapsulated within threads run
        by the NodePoolBuilder. This starts the needed sub-threads
        which will run forever until we tell them to stop.
        '''
        with self._start_lock:
            if self._running:
                raise exceptions.BuilderError('Cannot start, already running.')

            self._config = self._getAndValidateConfig()
            self._running = True

            # All worker threads share a single ZooKeeper instance/connection.
            self.zk = zk.ZooKeeper()
            self.zk.connect(self._config.zookeeper_servers.values())

            self.log.debug('Starting listener for build jobs')

            # Create build and upload worker objects
            for i in range(self._num_builders):
                w = BuildWorker(i, self._config_path, self.build_interval,
                                self.zk, self.dib_cmd)
                w.start()
                self._build_workers.append(w)

            for i in range(self._num_uploaders):
                w = UploadWorker(i, self._config_path, self.upload_interval,
                                 self.zk)
                w.start()
                self._upload_workers.append(w)

            if self.cleanup_interval > 0:
                self._janitor = CleanupWorker(0, self._config_path,
                                              self.cleanup_interval, self.zk)
                self._janitor.start()

            # Wait until all threads are running. Otherwise, we have a race
            # on the worker _running attribute if shutdown() is called before
            # run() actually begins.
            workers = self._build_workers + self._upload_workers
            if self._janitor:
                workers += [self._janitor]
            while not all([x.running for x in (workers)]):
                time.sleep(0)
Beispiel #5
0
    def _activate_virtualenv(self):
        """Run as a pre-exec function to activate current virtualenv

        If we are invoked directly as /path/ENV/nodepool-builer (as
        done by an init script, for example) then /path/ENV/bin will
        not be in our $PATH, meaning we can't find disk-image-create.
        Apart from that, dib also needs to run in an activated
        virtualenv so it can find utils like dib-run-parts.  Run this
        before exec of dib to ensure the current virtualenv (if any)
        is activated.
        """
        if self._running_under_virtualenv():
            activate_this = os.path.join(sys.prefix, "bin", "activate_this.py")
            if not os.path.exists(activate_this):
                raise exceptions.BuilderError("Running in a virtualenv, but "
                                              "cannot find: %s" %
                                              activate_this)
            execfile(activate_this, dict(__file__=activate_this))
Beispiel #6
0
    def runForever(self):
        with self._start_lock:
            if self._running:
                raise exceptions.BuilderError('Cannot start, already running.')

            self.load_config(self._config_path)
            self._validate_config()

            self.build_workers = []
            self.upload_workers = []
            for i in range(self._build_workers):
                w = BuildWorker('Nodepool Builder Build Worker %s' % (i + 1, ),
                                builder=self)
                self._initializeGearmanWorker(
                    w, self._config.gearman_servers.values())
                self.build_workers.append(w)
            for i in range(self._upload_workers):
                w = UploadWorker('Nodepool Builder Upload Worker %s' %
                                 (i + 1, ),
                                 builder=self)
                self._initializeGearmanWorker(
                    w, self._config.gearman_servers.values())
                self.upload_workers.append(w)

            self._registerGearmanFunctions(self._config.diskimages.values())
            self._registerExistingImageUploads()

            self.log.debug('Starting listener for build jobs')

            self.threads = []
            for worker in self.build_workers + self.upload_workers:
                t = threading.Thread(target=worker.run)
                t.daemon = True
                t.start()
                self.threads.append(t)

            self._running = True

        for t in self.threads:
            t.join()

        self._running = False
Beispiel #7
0
    def to_path(self, images_dir, with_extension=True):
        my_path = os.path.join(images_dir, self.image_id)
        if with_extension:
            if self.extension is None:
                raise exceptions.BuilderError(
                    'Cannot specify image extension of None')
            my_path += '.' + self.extension

        md5_path = '%s.%s' % (my_path, 'md5')
        md5 = self._checksum(md5_path)
        if md5:
            self.md5_file = md5_path
            self.md5 = md5[0:32]

        sha256_path = '%s.%s' % (my_path, 'sha256')
        sha256 = self._checksum(sha256_path)
        if sha256:
            self.sha256_file = sha256_path
            self.sha256 = sha256[0:64]

        return my_path
Beispiel #8
0
    def _buildImage(self, build_id, diskimage):
        '''
        Run the external command to build the diskimage.

        :param str build_id: The ID for the build (used in image filename).
        :param diskimage: The diskimage as retrieved from our config file.

        :returns: An ImageBuild object of build-related data.

        :raises: BuilderError if we failed to execute the build command.
        '''
        base = "-".join([diskimage.name, build_id])
        image_file = DibImageFile(base)
        filename = image_file.to_path(self._config.imagesdir, False)

        env = os.environ.copy()
        env['DIB_RELEASE'] = diskimage.release
        env['DIB_IMAGE_NAME'] = diskimage.name
        env['DIB_IMAGE_FILENAME'] = filename

        # Note we use a reference to the nodepool config here so
        # that whenever the config is updated we get up to date
        # values in this thread.
        if self._config.elementsdir:
            env['ELEMENTS_PATH'] = self._config.elementsdir

        # send additional env vars if needed
        for k, v in diskimage.env_vars.items():
            env[k] = v

        img_elements = diskimage.elements
        img_types = ",".join(diskimage.image_types)

        qemu_img_options = ''
        if 'qcow2' in img_types:
            qemu_img_options = DEFAULT_QEMU_IMAGE_COMPAT_OPTIONS

        cmd = ('%s -x -t %s --checksum --no-tmpfs %s -o %s %s' %
               (self.dib_cmd, img_types, qemu_img_options, filename,
                img_elements))

        log = logging.getLogger("nodepool.image.build.%s" % (diskimage.name, ))

        self.log.info('Running %s' % cmd)

        try:
            p = subprocess.Popen(shlex.split(cmd),
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT,
                                 preexec_fn=self._activate_virtualenv,
                                 env=env)
        except OSError as e:
            raise exceptions.BuilderError("Failed to exec '%s'. Error: '%s'" %
                                          (cmd, e.strerror))

        while True:
            ln = p.stdout.readline()
            log.info(ln.strip())
            if not ln:
                break

        p.wait()

        # It's possible the connection to the ZK cluster could have been
        # interrupted during the build. If so, wait for it to return.
        # It could transition directly from SUSPENDED to CONNECTED, or go
        # through the LOST state before CONNECTED.
        while self._zk.suspended or self._zk.lost:
            self.log.info("ZooKeeper suspended during build. Waiting")
            time.sleep(SUSPEND_WAIT_TIME)

        build_data = zk.ImageBuild()
        build_data.builder = self._hostname

        if self._zk.didLoseConnection:
            self.log.info("ZooKeeper lost while building %s" % diskimage.name)
            self._zk.resetLostFlag()
            build_data.state = zk.FAILED
        elif p.returncode:
            self.log.info("DIB failed creating %s" % diskimage.name)
            build_data.state = zk.FAILED
        else:
            self.log.info("DIB image %s is built" % diskimage.name)
            build_data.state = zk.READY
            build_data.formats = list(diskimage.image_types)

            if self._statsd:
                # record stats on the size of each image we create
                for ext in img_types.split(','):
                    key = 'nodepool.dib_image_build.%s.%s.size' % (
                        diskimage.name, ext)
                    # A bit tricky because these image files may be sparse
                    # files; we only want the true size of the file for
                    # purposes of watching if we've added too much stuff
                    # into the image.  Note that st_blocks is defined as
                    # 512-byte blocks by stat(2)
                    size = os.stat("%s.%s" % (filename, ext)).st_blocks * 512
                    self.log.debug("%s created image %s.%s (size: %d) " %
                                   (diskimage.name, filename, ext, size))
                    self._statsd.gauge(key, size)

        return build_data
Beispiel #9
0
    def _runDibForImage(self, image, filename):
        env = os.environ.copy()

        env['DIB_RELEASE'] = image.release
        env['DIB_IMAGE_NAME'] = image.name
        env['DIB_IMAGE_FILENAME'] = filename
        # Note we use a reference to the nodepool config here so
        # that whenever the config is updated we get up to date
        # values in this thread.
        if self._config.elementsdir:
            env['ELEMENTS_PATH'] = self._config.elementsdir
        if self._config.scriptdir:
            env['NODEPOOL_SCRIPTDIR'] = self._config.scriptdir

        # send additional env vars if needed
        for k, v in image.env_vars.items():
            env[k] = v

        img_elements = image.elements
        img_types = ",".join(image.image_types)

        qemu_img_options = ''
        if 'qcow2' in img_types:
            qemu_img_options = DEFAULT_QEMU_IMAGE_COMPAT_OPTIONS

        if 'fake-' in image.name:
            dib_cmd = 'nodepool/tests/fake-image-create'
        else:
            dib_cmd = 'disk-image-create'

        cmd = ('%s -x -t %s --no-tmpfs %s -o %s %s' %
               (dib_cmd, img_types, qemu_img_options, filename, img_elements))

        log = logging.getLogger("nodepool.image.build.%s" %
                                (image.name,))

        self.log.info('Running %s' % cmd)

        try:
            p = subprocess.Popen(
                shlex.split(cmd),
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                env=env)
        except OSError as e:
            raise exceptions.BuilderError(
                "Failed to exec '%s'. Error: '%s'" % (cmd, e.strerror)
            )

        while True:
            ln = p.stdout.readline()
            log.info(ln.strip())
            if not ln:
                break

        p.wait()
        ret = p.returncode
        if ret:
            raise exceptions.DibFailedError(
                "DIB failed creating %s" % filename
            )
Beispiel #10
0
 def create_image(self, **kwargs):
     raise exceptions.BuilderError("Test fail image upload.")
Beispiel #11
0
    def _runDibForImage(self, image, filename):
        env = os.environ.copy()

        env['DIB_RELEASE'] = image.release
        env['DIB_IMAGE_NAME'] = image.name
        env['DIB_IMAGE_FILENAME'] = filename
        # Note we use a reference to the nodepool config here so
        # that whenever the config is updated we get up to date
        # values in this thread.
        if self._config.elementsdir:
            env['ELEMENTS_PATH'] = self._config.elementsdir
        if self._config.scriptdir:
            env['NODEPOOL_SCRIPTDIR'] = self._config.scriptdir

        # send additional env vars if needed
        for k, v in image.env_vars.items():
            env[k] = v

        img_elements = image.elements
        img_types = ",".join(image.image_types)

        qemu_img_options = ''
        if 'qcow2' in img_types:
            qemu_img_options = DEFAULT_QEMU_IMAGE_COMPAT_OPTIONS

        if 'fake-' in image.name:
            dib_cmd = 'nodepool/tests/fake-image-create'
        else:
            dib_cmd = 'disk-image-create'

        cmd = ('%s -x -t %s --no-tmpfs %s -o %s %s' %
               (dib_cmd, img_types, qemu_img_options, filename, img_elements))

        log = logging.getLogger("nodepool.image.build.%s" % (image.name, ))

        self.log.info('Running %s' % cmd)

        try:
            p = subprocess.Popen(shlex.split(cmd),
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT,
                                 env=env)
        except OSError as e:
            raise exceptions.BuilderError("Failed to exec '%s'. Error: '%s'" %
                                          (cmd, e.strerror))

        while True:
            ln = p.stdout.readline()
            log.info(ln.strip())
            if not ln:
                break

        p.wait()
        ret = p.returncode
        if ret:
            raise exceptions.DibFailedError("DIB failed creating %s" %
                                            filename)

        if self.statsd:
            # record stats on the size of each image we create
            for ext in img_types.split(','):
                key = 'nodepool.dib_image_build.%s.%s.size' % (image.name, ext)
                # A bit tricky because these image files may be sparse
                # files; we only want the true size of the file for
                # purposes of watching if we've added too much stuff
                # into the image.  Note that st_blocks is defined as
                # 512-byte blocks by stat(2)
                size = os.stat("%s.%s" % (filename, ext)).st_blocks * 512
                self.log.debug("%s created image %s.%s (size: %d) " %
                               (image.name, filename, ext, size))
                self.statsd.gauge(key, size)