Example #1
0
def validate_has_key(obj, key, fullname):
    if key not in obj:
        raise ExtensionError("Missing: {0}".format(fullname))
    def launch_command(self,
                       cmd,
                       timeout=300,
                       extension_error_code=1000,
                       env=None):
        begin_utc = datetime.datetime.utcnow()
        self.logger.verbose("Launch command: [{0}]", cmd)
        base_dir = self.get_base_dir()

        if env is None:
            env = {}
        env.update(os.environ)

        try:
            # This should be .run(), but due to the wide variety
            # of Python versions we must support we must use .communicate().
            # Some extensions erroneously begin cmd with a slash; don't interpret those
            # as root-relative. (Issue #1170)
            full_path = os.path.join(base_dir, cmd.lstrip(os.path.sep))

            def pre_exec_function():
                """
                Change process state before the actual target process is started. Effectively, this runs between
                the fork() and the exec() of sub-process creation.
                :return:
                """
                os.setsid()
                CGroups.add_to_extension_cgroup(self.ext_handler.name)

            process = subprocess.Popen(full_path,
                                       shell=True,
                                       cwd=base_dir,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
                                       env=env,
                                       preexec_fn=pre_exec_function)
        except OSError as e:
            raise ExtensionError("Failed to launch '{0}': {1}".format(
                full_path, e.strerror),
                                 code=extension_error_code)

        cg = CGroups.for_extension(self.ext_handler.name)
        CGroupsTelemetry.track_extension(self.ext_handler.name, cg)
        msg = capture_from_process(process, cmd, timeout, extension_error_code)

        ret = process.poll()
        if ret is None:
            raise ExtensionError(
                "Process {0} was not terminated: {1}\n{2}".format(
                    process.pid, cmd, msg),
                code=extension_error_code)
        if ret != 0:
            raise ExtensionError("Non-zero exit code: {0}, {1}\n{2}".format(
                ret, cmd, msg),
                                 code=extension_error_code)

        duration = elapsed_milliseconds(begin_utc)
        log_msg = "{0}\n{1}".format(
            cmd, "\n".join([line for line in msg.split('\n') if line != ""]))
        self.logger.verbose(log_msg)
        self.report_event(message=log_msg, duration=duration, log_event=False)
Example #3
0
    def decide_version(self):
        self.logger.verbose("Decide which version to use")
        try:
            pkg_list = self.protocol.get_ext_handler_pkgs(self.ext_handler)
        except ProtocolError as e:
            raise ExtensionError("Failed to get ext handler pkgs", e)

        # Determine the desired and installed versions
        requested_version = FlexibleVersion(
            self.ext_handler.properties.version)
        installed_version = FlexibleVersion(self.get_installed_version())
        if installed_version is None:
            installed_version = requested_version

        # Divide packages
        # - Find the installed package (its version must exactly match)
        # - Find the internal candidate (its version must exactly match)
        # - Separate the public packages
        internal_pkg = None
        installed_pkg = None
        public_pkgs = []
        for pkg in pkg_list.versions:
            pkg_version = FlexibleVersion(pkg.version)
            if pkg_version == installed_version:
                installed_pkg = pkg
            if pkg.isinternal and pkg_version == requested_version:
                internal_pkg = pkg
            if not pkg.isinternal:
                public_pkgs.append(pkg)

        internal_version = FlexibleVersion(internal_pkg.version) \
                            if internal_pkg is not None \
                            else FlexibleVersion()
        public_pkgs.sort(key=lambda pkg: FlexibleVersion(pkg.version),
                         reverse=True)

        # Determine the preferred version and type of upgrade occurring
        preferred_version = max(requested_version, installed_version)
        is_major_upgrade = preferred_version.major > installed_version.major
        allow_minor_upgrade = self.ext_handler.properties.upgradePolicy == 'auto'

        # Find the first public candidate which
        # - Matches the preferred major version
        # - Does not upgrade to a new, disallowed major version
        # - And only increments the minor version if allowed
        # Notes:
        # - The patch / hotfix version is not considered
        public_pkg = None
        for pkg in public_pkgs:
            pkg_version = FlexibleVersion(pkg.version)
            if pkg_version.major == preferred_version.major \
                and (not pkg.disallow_major_upgrade or not is_major_upgrade) \
                and (allow_minor_upgrade or pkg_version.minor == preferred_version.minor):
                public_pkg = pkg
                break

        # If there are no candidates, locate the highest public version whose
        # major matches that installed
        if internal_pkg is None and public_pkg is None:
            for pkg in public_pkgs:
                pkg_version = FlexibleVersion(pkg.version)
                if pkg_version.major == installed_version.major:
                    public_pkg = pkg
                    break

        public_version = FlexibleVersion(public_pkg.version) \
                            if public_pkg is not None \
                            else FlexibleVersion()

        # Select the candidate
        # - Use the public candidate if there is no internal candidate or
        #   the public is more recent (e.g., a hotfix patch)
        # - Otherwise use the internal candidate
        if internal_pkg is None or (public_pkg is not None
                                    and public_version > internal_version):
            selected_pkg = public_pkg
        else:
            selected_pkg = internal_pkg

        selected_version = FlexibleVersion(selected_pkg.version) \
                            if selected_pkg is not None \
                            else FlexibleVersion()

        # Finally, update the version only if not downgrading
        # Note:
        #  - A downgrade, which will be bound to the same major version,
        #    is allowed if the installed version is no longer available
        if selected_pkg is None \
            or (installed_pkg is not None and selected_version < installed_version):
            self.pkg = installed_pkg
            self.ext_handler.properties.version = installed_version
        else:
            self.pkg = selected_pkg
            self.ext_handler.properties.version = selected_pkg.version

        # Note if the selected package is greater than that installed
        if installed_pkg is None \
            or FlexibleVersion(self.pkg.version) > FlexibleVersion(installed_pkg.version):
            self.is_upgrade = True

        if self.pkg is None:
            raise ExtensionError("Failed to find any valid extension package")

        self.logger.verbose("Use version: {0}", self.pkg.version)
        return
def validate_in_range(val, valid_range, name):
    if val not in valid_range:
        raise ExtensionError("Invalid {0}: {1}".format(name, val))
    def download(self):
        begin_utc = datetime.datetime.utcnow()
        self.logger.verbose("Download extension package")
        self.set_operation(WALAEventOperation.Download)

        if self.pkg is None:
            raise ExtensionError("No package uri found")

        uris_shuffled = self.pkg.uris
        random.shuffle(uris_shuffled)
        file_downloaded = False

        for uri in uris_shuffled:
            try:
                destination = os.path.join(conf.get_lib_dir(),
                                           os.path.basename(uri.uri) + ".zip")
                file_downloaded = self.protocol.download_ext_handler_pkg(
                    uri.uri, destination)

                if file_downloaded and os.path.exists(destination):
                    self.pkg_file = destination
                    break

            except Exception as e:
                logger.warn("Error while downloading extension: {0}", ustr(e))

        if not file_downloaded:
            raise ExtensionError("Failed to download extension", code=1001)

        self.logger.verbose("Unzip extension package")
        try:
            zipfile.ZipFile(self.pkg_file).extractall(self.get_base_dir())
            os.remove(self.pkg_file)
        except IOError as e:
            fileutil.clean_ioerror(e,
                                   paths=[self.get_base_dir(), self.pkg_file])
            raise ExtensionError(u"Failed to unzip extension package",
                                 e,
                                 code=1001)

        # Add user execute permission to all files under the base dir
        for file in fileutil.get_all_files(self.get_base_dir()):
            fileutil.chmod(file, os.stat(file).st_mode | stat.S_IXUSR)

        duration = elapsed_milliseconds(begin_utc)
        self.report_event(message="Download succeeded", duration=duration)

        self.logger.info("Initialize extension directory")
        # Save HandlerManifest.json
        man_file = fileutil.search_file(self.get_base_dir(),
                                        'HandlerManifest.json')

        if man_file is None:
            raise ExtensionError("HandlerManifest.json not found")

        try:
            man = fileutil.read_file(man_file, remove_bom=True)
            fileutil.write_file(self.get_manifest_file(), man)
        except IOError as e:
            fileutil.clean_ioerror(e,
                                   paths=[self.get_base_dir(), self.pkg_file])
            raise ExtensionError(u"Failed to save HandlerManifest.json", e)

        # Create status and config dir
        try:
            status_dir = self.get_status_dir()
            fileutil.mkdir(status_dir, mode=0o700)

            seq_no, status_path = self.get_status_file_path()
            if status_path is not None:
                now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
                status = {
                    "version": 1.0,
                    "timestampUTC": now,
                    "status": {
                        "name": self.ext_handler.name,
                        "operation": "Enabling Handler",
                        "status": "transitioning",
                        "code": 0
                    }
                }
                fileutil.write_file(status_path, json.dumps(status))

            conf_dir = self.get_conf_dir()
            fileutil.mkdir(conf_dir, mode=0o700)

        except IOError as e:
            fileutil.clean_ioerror(e,
                                   paths=[self.get_base_dir(), self.pkg_file])
            raise ExtensionError(u"Failed to create status or config dir", e)

        # Save HandlerEnvironment.json
        self.create_handler_env()
 def __init__(self, data):
     if data is None or data['handlerManifest'] is None:
         raise ExtensionError('Malformed manifest file.')
     self.data = data
Example #7
0
 def update_settings_file(self, settings_file, settings):
     settings_file = os.path.join(self.get_conf_dir(), settings_file)
     try:
         fileutil.write_file(settings_file, settings)
     except IOError as e:
         raise ExtensionError(u"Failed to update settings file", e)
Example #8
0
    def handle_ext_handler(self, ext_handler, etag):
        ext_handler_i = ExtHandlerInstance(ext_handler, self.protocol)

        try:
            state = ext_handler.properties.state
            # The extension is to be enabled, there is an upgrade GUID
            # and the GUID is NOT new
            if state == u"enabled" and \
                    ext_handler.properties.upgradeGuid is not None and \
                    not self.is_new_guid(ext_handler):
                ext_handler_i.ext_handler.properties.version = ext_handler_i.get_installed_version()
                ext_handler_i.set_logger()
                if self.last_etag != etag:
                    self.set_log_upgrade_guid(ext_handler, True)

                msg = "New GUID is the same as the old GUID. Exiting without upgrading."
                if self.get_log_upgrade_guid(ext_handler):
                    ext_handler_i.logger.info(msg)
                    self.set_log_upgrade_guid(ext_handler, False)
                ext_handler_i.set_handler_state(ExtHandlerState.Enabled)
                ext_handler_i.set_handler_status(status="Ready", message="No change")
                ext_handler_i.set_operation(WALAEventOperation.SkipUpdate)
                ext_handler_i.report_event(message=ustr(msg), is_success=True)
                return

            self.set_log_upgrade_guid(ext_handler, True)
            ext_handler_i.decide_version(target_state=state)
            self.get_artifact_error_state.reset()
            if not ext_handler_i.is_upgrade and self.last_etag == etag:
                if self.log_etag:
                    ext_handler_i.logger.verbose("Version {0} is current for etag {1}",
                                                 ext_handler_i.pkg.version,
                                                 etag)
                    self.log_etag = False
                return

            self.log_etag = True

            ext_handler_i.logger.info("Target handler state: {0}", state)
            if state == u"enabled":
                self.handle_enable(ext_handler_i)
                if ext_handler.properties.upgradeGuid is not None:
                    ext_handler_i.logger.info("New Upgrade GUID: {0}", ext_handler.properties.upgradeGuid)
                    self.last_upgrade_guids[ext_handler.name] = (ext_handler.properties.upgradeGuid, True)
            elif state == u"disabled":
                self.handle_disable(ext_handler_i)
                # Remove the GUID from the dictionary so that it is upgraded upon re-enable
                self.last_upgrade_guids.pop(ext_handler.name, None)
            elif state == u"uninstall":
                self.handle_uninstall(ext_handler_i)
                # Remove the GUID from the dictionary so that it is upgraded upon re-install
                self.last_upgrade_guids.pop(ext_handler.name, None)
            else:
                message = u"Unknown ext handler state:{0}".format(state)
                raise ExtensionError(message)
        except Exception as e:
            msg = ustr(e)
            ext_handler_i.set_handler_status(message=msg, code=-1)

            self.get_artifact_error_state.incr()
            if self.get_artifact_error_state.is_triggered():
                report_event(op=WALAEventOperation.GetArtifactExtended,
                             message="Failed to get artifact for over "
                                     "{0}: {1}".format(self.get_artifact_error_state.min_timedelta, msg),
                             is_success=False)
                self.get_artifact_error_state.reset()
            else:
                ext_handler_i.logger.warn(msg)