示例#1
0
    def set_team_personnel(self):
        """Set the personnel working for this team.

        In this method, we set attributes pertaining to the actual baseball-class objects
        corresponding to the employees of this organization. This method may be called any
        time an employee in the organization quits, retires, is fired, or dies.
        """
        # Set team owner
        owner_person = next(e for e in self.organization.employees
                            if isinstance(e, BaseballTeamOwner)).person
        self.owner = Owner(
            person=owner_person
        ) if not owner_person.team_owner else owner_person.team_owner
        # Set manager
        manager_person = next(e for e in self.organization.employees
                              if isinstance(e, BaseballManager)).person
        self.manager = (Manager(person=manager_person, team=self) if
                        not manager_person.manager else manager_person.manager)
        # Set scout
        scout_person = next(e for e in self.organization.employees
                            if isinstance(e, BaseballScout)).person
        self.scout = Scout(
            person=scout_person,
            team=self) if not scout_person.scout else scout_person.scout
        # Set personnel attribute
        self.personnel = {self.owner, self.manager, self.scout}
        for p in self.personnel:
            p.team = self
示例#2
0
    def run(self):
        LOG.debug('Leader.run()')
        ballot_num = (0,self.communicator.identity('leader')[0])
        LOG.debug("BALLOT: " + str(ballot_num))
        active = False
        proposals = []
   
        # Spawn a scout.
        me = self.communicator.identity('leader')
        Scout(me, self.acceptors, ballot_num, self.communicator).start()

        while True:
            sender, msg = self.receive()
            LOG.debug('Leader.receive: (%s,%s)' % (sender, msg))
            msg = msg.split(':')
            
            # Case 1
            if msg[0] == 'propose':
                LOG.debug("Leader: Inside propose")
                sp = ast.literal_eval(msg[1])
                s = sp[0]
                p = sp[1]
                if all([proposal[0] != s for proposal in proposals]):
                    # proposals = proposals union [sp]
                    if sp not in proposals:
                        proposals.append(sp)
                    if active:
                        bsp = (ballot_num, s, p)
                        LOG.debug("Leader spawning COM: " + str(bsp)) 
                        # Spawn commander.
                        Commander(self.acceptors, self.replicas, bsp, self.communicator).start()
               
            # Case 2
            if msg[0] == 'adopted':
                LOG.debug("--------------------ADOPTED-----------------")
                LOG.debug("PROPOSALS: " + str(proposals))
                pvalues = ast.literal_eval(msg[2])
                new_sp_list = self.pmax_op(pvalues)
                proposals = self.xor(proposals, new_sp_list)
                LOG.debug("After xor: " + str(proposals))
                for proposal in proposals:
                    bsp = (ast.literal_eval(msg[1]), proposal[0], proposal[1])
                    LOG.debug("Leader spawning COM: " + str(bsp))
                    Commander(self.acceptors, self.replicas, bsp, self.communicator).start()
                active = True
                
            # Case 3
            if msg[0] == "preempted":
                b = ast.literal_eval(msg[1])
                if b > ballot_num:
                    active = False
                    ballot_num[0] = b[0] + 1
                    # Spawn Scout.
                    time.sleep(random.random())
                    me = self.communicator.identity('leader')
                    Scout(me, self.acceptors, ballot_num, self.communicator).start()
示例#3
0
    def body(self):
        # print "Here I am: ", self.id
        Scout(self.env,
              "scout:%s:%s" % (str(self.id), str(self.ballot_number)), self.id,
              self.config.acceptors, self.ballot_number)
        while True:
            msg = self.getNextMessage()
            if isinstance(msg, ProposeMessage):
                if msg.slot_number not in self.proposals:
                    self.proposals[msg.slot_number] = msg.command
                    if self.active:
                        Commander(
                            self.env, "commander:%s:%s:%s" %
                            (str(self.id), str(self.ballot_number),
                             str(msg.slot_number)), self.id,
                            self.config.acceptors, self.config.replicas,
                            self.ballot_number, msg.slot_number, msg.command)
            elif isinstance(msg, AdoptedMessage):
                if self.ballot_number == msg.ballot_number:
                    pmax = {}
                    for pv in msg.accepted:
                        if pv.slot_number not in pmax or \
                              pmax[pv.slot_number] < pv.ballot_number:
                            pmax[pv.slot_number] = pv.ballot_number
                            self.proposals[pv.slot_number] = pv.command
                    for sn in self.proposals:
                        Commander(
                            self.env, "commander:%s:%s:%s" %
                            (str(self.id), str(self.ballot_number), str(sn)),
                            self.id, self.config.acceptors,
                            self.config.replicas, self.ballot_number, sn,
                            self.proposals.get(sn))
                    self.active = True
            elif isinstance(msg, PreemptedMessage):
                if msg.ballot_number > self.ballot_number:
                    self.active = False
                    self.ballot_number = BallotNumber(
                        msg.ballot_number.round + 1, self.id)
                    Scout(
                        self.env, "scout:%s:%s" %
                        (str(self.id), str(self.ballot_number)), self.id,
                        self.config.acceptors, self.ballot_number)

            elif isinstance(msg, KillMessage):
                break
            else:
                break
示例#4
0
    def body(self):
        if self.verbose:
            print("Here I am: ", self.id)

        Scout(self.env, f"scout:{self.id}:{self.ballot_number}", self.id,
              self.config.acceptors, self.ballot_number)
        while True:
            msg = self.getNextMessage()
            if isinstance(msg, ProposeMessage):
                if msg.slot_number not in self.proposals:
                    self.proposals[msg.slot_number] = msg.command
                    if self.active:
                        Commander(
                            self.env,
                            f"commander:{self.id}:{self.ballot_number}:\
                                    {msg.slot_number}", self.id,
                            self.config.acceptors, self.config.replicas,
                            self.ballot_number, msg.slot_number, msg.command)
            elif isinstance(msg, AdoptedMessage):
                if self.ballot_number == msg.ballot_number:
                    pmax = {}
                    for pv in msg.accepted:
                        if pv.slot_number not in pmax or \
                              pmax[pv.slot_number] < pv.ballot_number:
                            pmax[pv.slot_number] = pv.ballot_number
                            self.proposals[pv.slot_number] = pv.command
                    for sn in self.proposals:
                        Commander(
                            self.env,
                            f"commander:{self.id}:{self.ballot_number}:\
                                    {sn}", self.id, self.config.acceptors,
                            self.config.replicas, self.ballot_number, sn,
                            self.proposals.get(sn))
                    self.active = True
            elif isinstance(msg, PreemptedMessage):
                if msg.ballot_number > self.ballot_number:
                    self.ballot_number =\
                            BallotNumber(msg.ballot_number.round+1,
                                         self.id)
                    Scout(self.env, f"scout:{self.id}:{self.ballot_number}",
                          self.id, self.config.acceptors, self.ballot_number)
                self.active = False
            else:
                print("Leader: unknown msg type")
示例#5
0
    def body(self):
        print "Here I am: ", self.me

        Scout(self.env,
              "scout:%s:%s" % (str(self.me), str(self.ballot_number)), self.me,
              self.config.acceptors, self.ballot_number)
        while True:
            msg = self.getNextMessage()
            if isinstance(msg, ProposeMessage):
                if msg.slot_number not in self.proposals:
                    self.proposals[msg.slot_number] = msg.command
                    if self.active:
                        Commander(
                            self.env, "commander:%s:%s:%s" %
                            (str(self.me), str(self.ballot_number),
                             str(msg.slot_number)), self.me,
                            self.config.acceptors, self.config.replicas,
                            self.ballot_number, msg.slot_number, msg.command)
            elif isinstance(msg, AdoptedMessage):
                if self.ballot_number == msg.ballot_number:
                    for slot_number in msg.accepted.pvalues:
                        self.proposals[slot_number] = msg.accepted.pvalues[
                            slot_number].command
                    for sn in self.proposals:
                        Commander(
                            self.env, "commander:%s:%s:%s" %
                            (str(self.me), str(self.ballot_number), str(sn)),
                            self.me, self.config.acceptors,
                            self.config.replicas, self.ballot_number, sn,
                            self.proposals.get(sn))
                    self.active = True
            elif isinstance(msg, PreemptedMessage):
                if self.ballot_number < msg.ballot_number:
                    self.ballot_number = BallotNumber(
                        msg.ballot_number.round + 1, self.me)
                    Scout(
                        self.env, "scout:%s:%s" %
                        (str(self.me), str(self.ballot_number)), self.me,
                        self.config.acceptors, self.ballot_number)
                    self.active = False
            else:
                print "Leader: unknown msg type"
示例#6
0
    def scout(self) -> Optional[Scout]:
        if not self._scout:
            try:
                self._scout = Scout(app="ambassador", version=self.version, install_id=self.install_id)
                self._scout_error = None
                self.logger.debug("Scout connection established")
            except OSError as e:
                self._scout = None
                self._scout_error = str(e)
                self.logger.debug("Scout connection failed, will retry later: %s" % self._scout_error)

        return self._scout
示例#7
0
def search_book_index(query: str,
                      limit: int,
                      database: str
                      ) -> List[Dict[str, str]]:
    """Search book index uses Scout search engine to fetch results.

    Returns a list of book results for a single query.
    .. code-block::

        [
            {
                "id": "34",
                "summary": "The Book in Three Sentences: Ultimately, profit is the only valid..",
                "query": "autistic"
            },
            {
                "id": "3",
                "summary": "The Book in Three Sentences: Autism, that truth was committing..",
                "query": "autistic"
            },
        ]

    :param query: User input query terms.
    :type query: str
    :param limit: Maximum results to fetch.
    :type limit: int
    :param database: Scout index SQLite3 database file.
    :type database: str
    :return: List of results with Book's id, summary and query.
    :rtype: List[Dict[str, str]]

    """
    sc = Scout(database)
    return [{
        "id": r['id'],
        "summary": r['summary'],
        "query": query
    } for r in sc.search(query, limit)]
    def scout(self) -> Optional[Scout]:
        if not self._scout:
            if self._local_only:
                self._scout = LocalScout(logger=self.logger,
                                         app=self.app, version=self.version, install_id=self.install_id)
                self.logger.debug("LocalScout initialized")
            else:
                try:
                    self._scout = Scout(app=self.app, version=self.version, install_id=self.install_id)
                    self._scout_error = None
                    self.logger.debug("Scout connection established")
                except OSError as e:
                    self._scout = None
                    self._scout_error = str(e)
                    self.logger.debug("Scout connection failed, will retry later: %s" % self._scout_error)

        return self._scout
示例#9
0
    def set_team_personnel(self):
        """Set the personnel working for this team.

        In this method, we set attributes pertaining to the actual baseball-class objects
        corresponding to the employees of this organization. This method may be called any
        time an employee in the organization quits, retires, is fired, or dies.
        """
        # Set team owner
        owner_person = next(e for e in self.organization.employees if isinstance(e, BaseballTeamOwner)).person
        self.owner = Owner(person=owner_person) if not owner_person.team_owner else owner_person.team_owner
        # Set manager
        manager_person = next(e for e in self.organization.employees if isinstance(e, BaseballManager)).person
        self.manager = (
            Manager(person=manager_person, team=self) if not manager_person.manager else manager_person.manager
        )
        # Set scout
        scout_person = next(e for e in self.organization.employees if isinstance(e, BaseballScout)).person
        self.scout = Scout(person=scout_person, team=self) if not scout_person.scout else scout_person.scout
        # Set personnel attribute
        self.personnel = {self.owner, self.manager, self.scout}
        for p in self.personnel:
            p.team = self
示例#10
0
    def __init__(self,
                 config_dir_path,
                 schema_dir_path="schemas",
                 template_dir_path="templates"):
        self.config_dir_path = config_dir_path
        self.schema_dir_path = schema_dir_path
        self.template_dir_path = template_dir_path

        self.logger = logging.getLogger("ambassador.config")

        if not AmbassadorConfig.scout:
            self.logger.debug("Scout version %s" %
                              AmbassadorConfig.scout_version)
            self.logger.debug("runtime: %s" % AmbassadorConfig.runtime)

        try:
            AmbassadorConfig.scout = Scout(
                app="ambassador",
                version=AmbassadorConfig.scout_version,
                id_plugin=Scout.configmap_install_id_plugin,
                id_plugin_args={"namespace": AmbassadorConfig.namespace})
        except OSError as e:
            self.logger.warning("couldn't do version check: %s" % str(e))

        self.schemas = {}
        self.config = {}
        self.envoy_config = {}
        self.envoy_clusters = {}
        self.envoy_routes = {}

        self.sources = {
            "--internal--": {
                "kind":
                "Internal",
                "version":
                "v0",
                "name":
                "Ambassador Internals",
                "filename":
                "--internal--",
                "index":
                0,
                "description":
                "The '--internal--' source marks objects created by Ambassador's internal logic."
            },
            "--diagnostics--": {
                "kind":
                "diagnostics",
                "version":
                "v0",
                "name":
                "Ambassador Diagnostics",
                "filename":
                "--diagnostics--",
                "index":
                0,
                "description":
                "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output."
            }
        }

        self.source_map = {'--internal--': {'--internal--': True}}

        self.default_liveness_probe = {
            "enabled": True,
            "prefix": "/ambassador/v0/check_alive",
            "rewrite": "/ambassador/v0/check_alive",
            # "service" gets added later
        }

        self.default_readiness_probe = {
            "enabled": True,
            "prefix": "/ambassador/v0/check_ready",
            "rewrite": "/ambassador/v0/check_ready",
            # "service" gets added later
        }

        self.default_diagnostics = {
            "enabled": True,
            "prefix": "/ambassador/v0/",
            "rewrite": "/ambassador/v0/",
            # "service" gets added later
        }

        self.default_tls_config = {
            "server": {
                "cert_chain_file": "/etc/certs/tls.crt",
                "private_key_file": "/etc/certs/tls.key"
            },
            "client": {
                "cacert_chain_file": "/etc/cacert/fullchain.pem"
            }
        }

        self.tls_config = None

        self.errors = {}
        self.fatal_errors = 0
        self.object_errors = 0

        if not os.path.isdir(self.config_dir_path):
            raise Exception(
                "ERROR ERROR ERROR configuration directory %s does not exist; exiting"
                % self.config_dir_path)

        for dirpath, dirnames, filenames in os.walk(self.config_dir_path,
                                                    topdown=True):
            # Modify dirnames in-place (dirs[:]) to remove any weird directories
            # whose names start with '.' -- why? because my GKE cluster mounts my
            # ConfigMap with a self-referential directory named
            # /etc/ambassador-config/..9989_25_09_15_43_06.922818753, and if we don't
            # ignore that, we end up trying to read the same config files twice, which
            # triggers the collision checks. Sigh.

            dirnames[:] = sorted(
                [d for d in dirnames if not d.startswith('.')])

            # self.logger.debug("WALK %s: dirs %s, files %s" % (dirpath, dirnames, filenames))

            for filename in sorted(
                [x for x in filenames if x.endswith(".yaml")]):
                self.filename = filename

                filepath = os.path.join(dirpath, filename)

                try:
                    # XXX This is a bit of a hack -- yaml.safe_load_all returns a
                    # generator, and if we don't use list() here, any exception
                    # dealing with the actual object gets deferred
                    objects = list(yaml.safe_load_all(open(filepath, "r")))
                except Exception as e:
                    self.logger.error("%s: could not parse YAML: %s" %
                                      (filepath, e))
                    self.fatal_errors += 1
                    continue

                self.ocount = 0
                for obj in objects:
                    self.ocount += 1

                    rc = self.process_object(obj)

                    if not rc:
                        # Object error. Not good but we'll allow the system to start.
                        self.post_error(rc)

        if self.fatal_errors:
            # Kaboom.
            raise Exception(
                "ERROR ERROR ERROR Unparseable configuration; exiting")

        if self.errors:
            self.logger.error(
                "ERROR ERROR ERROR Starting with configuration errors")

        self.generate_intermediate_config()
示例#11
0
class Config(object):
    # Weird stuff. The build version looks like
    #
    # 0.12.0                    for a prod build, or
    # 0.12.1-b2.da5d895.DIRTY   for a dev build (in this case made from a dirty true)
    #
    # Now:
    # - Scout needs a build number (semver "+something") to flag a non-prod release;
    #   but
    # - DockerHub cannot use a build number at all; but
    # - 0.12.1-b2 comes _before_ 0.12.1+b2 in SemVer land.
    #
    # FFS.
    #
    # We cope with this by transforming e.g.
    #
    # 0.12.1-b2.da5d895.DIRTY into 0.12.1-b2+da5d895.DIRTY
    #
    # for Scout.

    scout_version = Version

    if '-' in scout_version:
        # Dev build!
        v, p = scout_version.split('-')
        p, b = p.split('.', 1) if ('.' in p) else (0, p)

        scout_version = "%s-%s+%s" % (v, p, b)

    # Use scout_version here, not __version__, because the version
    # coming back from Scout will use build numbers for dev builds, but
    # __version__ won't, and we need to be consistent for comparison.
    current_semver = get_semver("current", scout_version)

    runtime = "kubernetes" if os.environ.get('KUBERNETES_SERVICE_HOST',
                                             None) else "docker"
    namespace = os.environ.get('AMBASSADOR_NAMESPACE', 'default')
    scout_install_id = os.environ.get('AMBASSADOR_SCOUT_ID', None)

    _scout_args = dict(
        app="ambassador",
        version=scout_version,
    )

    if scout_install_id:
        _scout_args['install_id'] = scout_install_id
    else:
        _scout_args['id_plugin'] = Scout.configmap_install_id_plugin
        _scout_args['id_plugin_args'] = {"namespace": namespace}

    try:
        scout = Scout(**_scout_args)
        scout_error = None
    except OSError as e:
        scout_error = e

    scout_latest_version = None
    scout_latest_semver = None
    scout_notices = []

    @classmethod
    def scout_report(klass, force_result=None, **kwargs):
        result = force_result

        if not result:
            if Config.scout:
                if 'runtime' not in kwargs:
                    kwargs['runtime'] = Config.runtime

                result = Config.scout.report(**kwargs)
            else:
                result = {"scout": "unavailable"}

        _notices = []

        if not Config.current_semver:
            _notices.append({
                "level":
                "warning",
                "message":
                "Ambassador has bad version '%s'??!" % Config.scout_version
            })

        # Do version & notices stuff.
        if 'latest_version' in result:
            latest_version = result['latest_version']
            latest_semver = get_semver("latest", latest_version)

            if latest_semver:
                Config.scout_latest_version = latest_version
                Config.scout_latest_semver = latest_semver
            else:
                _notices.append({
                    "level":
                    "warning",
                    "message":
                    "Scout returned bad version '%s'??!" % latest_version
                })

        if (Config.scout_latest_semver
                and ((not Config.current_semver) or
                     (Config.scout_latest_semver > Config.current_semver))):
            _notices.append({
                "level":
                "info",
                "message":
                "Upgrade available! to Ambassador version %s" %
                Config.scout_latest_semver
            })

        if 'notices' in result:
            _notices.extend(result['notices'])

        Config.scout_notices = _notices

        return result

    def __init__(self,
                 config_dir_path,
                 k8s=False,
                 schema_dir_path=None,
                 template_dir_path=None):
        self.config_dir_path = config_dir_path

        if not template_dir_path:
            template_dir_path = resource_filename(
                Requirement.parse("ambassador"), "templates")

        if not schema_dir_path:
            schema_dir_path = resource_filename(
                Requirement.parse("ambassador"), "schemas")

        self.schema_dir_path = schema_dir_path
        self.template_dir_path = template_dir_path

        self.logger = logging.getLogger("ambassador.config")

        self.logger.debug("Scout version %s" % Config.scout_version)
        self.logger.debug("Runtime       %s" % Config.runtime)

        self.logger.debug("CONFIG DIR    %s" %
                          os.path.abspath(self.config_dir_path))
        self.logger.debug("TEMPLATE DIR  %s" %
                          os.path.abspath(self.template_dir_path))
        self.logger.debug("SCHEMA DIR    %s" %
                          os.path.abspath(self.schema_dir_path))

        if Config.scout_error:
            self.logger.warning("Couldn't do version check: %s" %
                                str(Config.scout_error))

        self.schemas = {}
        self.config = {}
        self.tls_contexts = {}

        self.envoy_config = {}
        self.envoy_clusters = {}
        self.envoy_routes = {}

        self.sources = {
            "--internal--": {
                "kind":
                "Internal",
                "version":
                "v0",
                "name":
                "Ambassador Internals",
                "filename":
                "--internal--",
                "index":
                0,
                "description":
                "The '--internal--' source marks objects created by Ambassador's internal logic."
            },
            "--diagnostics--": {
                "kind":
                "diagnostics",
                "version":
                "v0",
                "name":
                "Ambassador Diagnostics",
                "filename":
                "--diagnostics--",
                "index":
                0,
                "description":
                "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output."
            }
        }

        self.source_map = {'--internal--': {'--internal--': True}}

        self.default_liveness_probe = {
            "enabled": True,
            "prefix": "/ambassador/v0/check_alive",
            "rewrite": "/ambassador/v0/check_alive",
            # "service" gets added later
        }

        self.default_readiness_probe = {
            "enabled": True,
            "prefix": "/ambassador/v0/check_ready",
            "rewrite": "/ambassador/v0/check_ready",
            # "service" gets added later
        }

        self.default_diagnostics = {
            "enabled": True,
            "prefix": "/ambassador/v0/",
            "rewrite": "/ambassador/v0/",
            # "service" gets added later
        }

        # 'server' and 'client' are special contexts. Others
        # use cert_chain_file defaulting to context.crt,
        # private_key_file (context.key), and cacert_chain_file
        # (context.pem).

        self.default_tls_config = {
            "server": {
                "cert_chain_file": "/etc/certs/tls.crt",
                "private_key_file": "/etc/certs/tls.key"
            },
            "client": {
                "cacert_chain_file": "/etc/cacert/fullchain.pem"
            }
        }

        self.tls_config = None

        self.errors = {}
        self.fatal_errors = 0
        self.object_errors = 0

        if not os.path.isdir(self.config_dir_path):
            raise Exception(
                "ERROR ERROR ERROR configuration directory %s does not exist; exiting"
                % self.config_dir_path)

        for dirpath, dirnames, filenames in os.walk(self.config_dir_path,
                                                    topdown=True):
            # Modify dirnames in-place (dirs[:]) to remove any weird directories
            # whose names start with '.' -- why? because my GKE cluster mounts my
            # ConfigMap with a self-referential directory named
            # /etc/ambassador-config/..9989_25_09_15_43_06.922818753, and if we don't
            # ignore that, we end up trying to read the same config files twice, which
            # triggers the collision checks. Sigh.

            dirnames[:] = sorted(
                [d for d in dirnames if not d.startswith('.')])

            # self.logger.debug("WALK %s: dirs %s, files %s" % (dirpath, dirnames, filenames))

            for filename in sorted(
                [x for x in filenames if x.endswith(".yaml")]):
                filepath = os.path.join(dirpath, filename)

                self.process_yaml(filename,
                                  open(filepath, "r").read(),
                                  k8s=k8s)

        if self.fatal_errors:
            # Kaboom.
            raise Exception(
                "ERROR ERROR ERROR Unparseable configuration; exiting")

        if self.errors:
            self.logger.error(
                "ERROR ERROR ERROR Starting with configuration errors")

        self.generate_intermediate_config()

    def process_yaml(self, filename, serialization, k8s=False):
        all_objects = []

        try:
            # XXX This is a bit of a hack -- yaml.safe_load_all returns a
            # generator, and if we don't use list() here, any exception
            # dealing with the actual object gets deferred
            ocount = 1

            for obj in yaml.safe_load_all(serialization):
                all_objects.append((filename, ocount, obj))
                ocount += 1
        except Exception as e:
            self.logger.error("%s: could not parse YAML: %s" % (filename, e))
            self.fatal_errors += 1

        for filename, ocount, obj in all_objects:
            self.filename = filename
            self.ocount = ocount

            if k8s:
                kind = obj.get('kind', None)

                if kind != "Service":
                    self.logger.info("%s/%s: ignoring K8s %s object" %
                                     (self.filename, self.ocount, kind))
                    continue

                metadata = obj.get('metadata', None)

                if not metadata:
                    self.logger.info("%s/%s: ignoring unannotated K8s %s" %
                                     (self.filename, self.ocount, kind))
                    continue

                annotations = metadata.get('annotations', None)

                if annotations:
                    annotations = annotations.get('getambassador.io/config',
                                                  None)

                # self.logger.info("annotations %s" % annotations)

                if not annotations:
                    self.logger.info(
                        "%s/%s: ignoring K8s %s without Ambassador annotation"
                        % (self.filename, self.ocount, kind))
                    continue

                self.process_yaml(filename + ":annotation", annotations)
            else:
                rc = self.process_object(obj)

                if not rc:
                    # Object error. Not good but we'll allow the system to start.
                    self.post_error(rc)

    def clean_and_copy(self, d):
        out = []

        for key in sorted(d.keys()):
            original = d[key]
            copy = dict(**original)

            if '_source' in original:
                del (original['_source'])

            if '_referenced_by' in original:
                del (original['_referenced_by'])

            out.append(copy)

        return out

    def current_source_key(self):
        return ("%s.%d" % (self.filename, self.ocount))

    def post_error(self, rc):
        source_map = self.source_map.setdefault(self.filename, {})
        source_map[self.current_source_key()] = True

        errors = self.errors.setdefault(self.current_source_key(), [])
        errors.append(rc.toDict())
        self.logger.error("%s: %s" % (self.current_source_key(), rc))

    def process_object(self, obj):
        try:
            obj_version = obj['apiVersion']
            obj_kind = obj['kind']
            obj_name = obj['name']
        except KeyError:
            return RichStatus.fromError("need apiVersion, kind, and name")

        # ...save the source info...
        source_key = "%s.%d" % (self.filename, self.ocount)
        self.sources[source_key] = {
            'kind': obj_kind,
            'version': obj_version,
            'name': obj_name,
            'filename': self.filename,
            'index': self.ocount,
            'yaml': yaml.safe_dump(obj, default_flow_style=False)
        }

        source_map = self.source_map.setdefault(self.filename, {})
        source_map[source_key] = True

        # OK. What is this thing?
        rc = self.validate_object(obj)

        if not rc:
            # Well that's no good.
            return rc

        # OK, so far so good. Grab the handler for this object type.
        handler_name = "handle_%s" % obj_kind.lower()
        handler = getattr(self, handler_name, None)

        if not handler:
            handler = self.save_object
            self.logger.warning("%s[%d]: no handler for %s, just saving" %
                                (self.filename, self.ocount, obj_kind))
        else:
            self.logger.debug("%s[%d]: handling %s..." %
                              (self.filename, self.ocount, obj_kind))

        try:
            handler(source_key, obj, obj_name, obj_kind, obj_version)
        except Exception as e:
            # Bzzzt.
            return RichStatus.fromError("could not process %s object: %s" %
                                        (obj_kind, e))

        # OK, all's well.
        return RichStatus.OK(msg="%s object processed successfully" % obj_kind)

    def validate_object(self, obj):
        # Each object must be a dict, and must include "apiVersion"
        # and "type" at toplevel.

        if not isinstance(obj, collections.Mapping):
            return RichStatus.fromError("not a dictionary")

        if not (("apiVersion" in obj) and ("kind" in obj) and ("name" in obj)):
            return RichStatus.fromError("must have apiVersion, kind, and name")

        obj_version = obj['apiVersion']
        obj_kind = obj['kind']
        obj_name = obj['name']

        if obj_version.startswith("ambassador/"):
            obj_version = obj_version.split('/')[1]
        else:
            return RichStatus.fromError("apiVersion %s unsupported" %
                                        obj_version)

        schema_key = "%s-%s" % (obj_version, obj_kind)

        schema = self.schemas.get(schema_key, None)

        if not schema:
            schema_path = os.path.join(self.schema_dir_path, obj_version,
                                       "%s.schema" % obj_kind)

            try:
                schema = json.load(open(schema_path, "r"))
            except OSError:
                self.logger.debug("no schema at %s, skipping" % schema_path)
            except json.decoder.JSONDecodeError as e:
                self.logger.warning("corrupt schema at %s, skipping (%s)" %
                                    (schema_path, e))

        if schema:
            self.schemas[schema_key] = schema
            try:
                jsonschema.validate(obj, schema)
            except jsonschema.exceptions.ValidationError as e:
                return RichStatus.fromError("not a valid %s: %s" %
                                            (obj_kind, e))

        return RichStatus.OK(msg="valid %s" % obj_kind,
                             details=(obj_kind, obj_version, obj_name))

    def safe_store(self,
                   source_key,
                   storage_name,
                   obj_name,
                   obj_kind,
                   value,
                   allow_log=True):
        storage = self.config.setdefault(storage_name, {})

        if obj_name in storage:
            # Oooops.
            raise Exception("%s[%d] defines %s %s, which is already present" %
                            (self.filename, self.ocount, obj_kind, obj_name))

        if allow_log:
            self.logger.debug("%s[%d]: saving %s %s" %
                              (self.filename, self.ocount, obj_kind, obj_name))

        storage[obj_name] = value
        return storage[obj_name]

    def save_object(self, source_key, obj, obj_name, obj_kind, obj_version):
        return self.safe_store(source_key, obj_kind, obj_name, obj_kind,
                               SourcedDict(_source=source_key, **obj))

    def handle_module(self, source_key, obj, obj_name, obj_kind, obj_version):
        return self.safe_store(
            source_key, "modules", obj_name, obj_kind,
            SourcedDict(_source=source_key, **obj['config']))

    def handle_mapping(self, source_key, obj, obj_name, obj_kind, obj_version):
        mapping = Mapping(source_key, **obj)

        return self.safe_store(source_key, "mappings", obj_name, obj_kind,
                               mapping)

    def diag_port(self):
        modules = self.config.get("modules", {})
        amod = modules.get("ambassador", {})

        return amod.get("diag_port", 8877)

    def diag_service(self):
        return "127.0.0.1:%d" % self.diag_port()

    def add_intermediate_cluster(self,
                                 _source,
                                 name,
                                 urls,
                                 type="strict_dns",
                                 lb_type="round_robin",
                                 cb_name=None,
                                 od_name=None,
                                 originate_tls=None,
                                 grpc=False):
        if name not in self.envoy_clusters:
            cluster = SourcedDict(_source=_source,
                                  _referenced_by=[_source],
                                  name=name,
                                  type=type,
                                  lb_type=lb_type,
                                  urls=urls)

            if cb_name and (cb_name in self.breakers):
                cluster['circuit_breakers'] = self.breakers[cb_name]
                self.breakers[cb_name]._mark_referenced_by(_source)

            if od_name and (od_name in self.outliers):
                cluster['outlier_detection'] = self.outliers[od_name]
                self.outliers[od_name]._mark_referenced_by(_source)

            if originate_tls == True:
                cluster['tls_context'] = {'_ambassador_enabled': True}
                cluster['tls_array'] = []
            elif (originate_tls and (originate_tls in self.tls_contexts)):
                cluster['tls_context'] = self.tls_contexts[originate_tls]
                self.tls_contexts[originate_tls]._mark_referenced_by(_source)

                tls_array = []

                for key, value in cluster['tls_context'].items():
                    if key.startswith('_'):
                        continue

                    tls_array.append({'key': key, 'value': value})

                cluster['tls_array'] = tls_array
            if grpc:
                cluster['features'] = 'http2'

            self.envoy_clusters[name] = cluster
        else:
            self.envoy_clusters[name]._mark_referenced_by(_source)

    def add_intermediate_route(self, _source, mapping, cluster_name):
        route = self.envoy_routes.get(mapping.group_id, None)

        if route:
            # Take the easy way out -- just add a new entry to this
            # route's set of weighted clusters.
            route["clusters"].append({
                "name":
                cluster_name,
                "weight":
                mapping.attrs.get("weight", None)
            })
            route._mark_referenced_by(_source)
            return

        # OK, if here, we don't have an extent route group for this Mapping. Make a
        # new one.
        route = mapping.new_route(cluster_name)
        self.envoy_routes[mapping.group_id] = route

    def service_tls_check(self, svc, context):
        originate_tls = False
        name_fields = None

        if svc.lower().startswith("http://"):
            originate_tls = False
            svc = svc[len("http://"):]
        elif svc.lower().startswith("https://"):
            originate_tls = True
            name_fields = ['otls']
            svc = svc[len("https://"):]
        elif context == True:
            originate_tls = True
            name_fields = ['otls']

        # Separate if here because you need to be able to specify a context
        # even after you say "https://" for the service.

        if context and (context != True):
            if context in self.tls_contexts:
                name_fields = ['otls', context]
                originate_tls = context
            else:
                self.logger.error(
                    "Originate-TLS context %s is not defined (mapping %s)" %
                    (context, mapping_name))

        port = 443 if originate_tls else 80
        context_name = "_".join(name_fields) if name_fields else None

        svc_url = 'tcp://%s' % svc

        if ':' not in svc:
            svc_url = '%s:%d' % (svc_url, port)

        return (svc, svc_url, originate_tls, context_name)

    def generate_intermediate_config(self):
        # First things first. The "Ambassador" module always exists; create it with
        # default values now.

        self.ambassador_module = SourcedDict(service_port=80,
                                             admin_port=8001,
                                             diag_port=8877,
                                             liveness_probe={"enabled": True},
                                             readiness_probe={"enabled": True},
                                             diagnostics={"enabled": True},
                                             tls_config=None)

        # Now we look at user-defined modules from our config...
        modules = self.config.get('modules', {})

        # ...most notably the 'ambassador' and 'tls' modules, which are handled first.
        amod = modules.get('ambassador', None)
        tmod = modules.get('tls', None)

        if amod or tmod:
            self.module_config_ambassador("ambassador", amod, tmod)

        # Next up: let's define initial clusters, routes, and filters.
        #
        # Our initial set of clusters just contains the one for our Courier container.
        # We start with the empty set and use add_intermediate_cluster() to make sure
        # that all the source-tracking stuff works out.
        #
        # Note that we use a map for clusters, not a list -- the reason is that
        # multiple mappings can use the same service, and we don't want multiple
        # clusters.
        self.envoy_clusters = {}
        # self.add_intermediate_cluster('--diagnostics--',
        #                               'cluster_diagnostics',
        #                               [ "tcp://%s" % self.diag_service() ],
        #                               type="logical_dns", lb_type="random")

        # Our initial set of routes is empty...
        self.envoy_routes = {}

        # ...and our initial set of filters is just the 'router' filter.
        #
        # !!!! WARNING WARNING WARNING !!!! Filters are actually ORDER-DEPENDENT.
        # We're kind of punting on that so far since we'll only ever add one filter
        # right now.
        self.envoy_config['filters'] = [
            SourcedDict(type="decoder", name="router", config={})
        ]

        # For mappings, start with empty sets for everything.
        mappings = self.config.get("mappings", {})

        self.breakers = self.config.get("CircuitBreaker", {})

        for key, breaker in self.breakers.items():
            breaker['_referenced_by'] = []

        self.outliers = self.config.get("OutlierDetection", {})

        for key, outlier in self.outliers.items():
            outlier['_referenced_by'] = []

        # OK. Given those initial sets, let's look over our global modules.
        for module_name in modules.keys():
            if (module_name == 'ambassador') or (module_name == 'tls'):
                continue

            handler_name = "module_config_%s" % module_name
            handler = getattr(self, handler_name, None)

            if not handler:
                self.logger.error(
                    "module %s: no configuration generator, skipping" %
                    module_name)
                continue

            handler(module_name, modules[module_name])

        # # Once modules are handled, we can set up our listeners...
        self.envoy_config['listeners'] = SourcedDict(
            _from=self.ambassador_module,
            service_port=self.ambassador_module["service_port"],
            admin_port=self.ambassador_module["admin_port"])

        self.default_liveness_probe['service'] = self.diag_service()
        self.default_readiness_probe['service'] = self.diag_service()
        self.default_diagnostics['service'] = self.diag_service()

        # ...TLS config, if necessary...
        if self.ambassador_module['tls_config']:
            self.logger.debug("USING TLS")
            self.envoy_config['tls'] = self.ambassador_module['tls_config']

        # ...and probes, if configured.
        for name, cur, dflt in [
            ("liveness", self.ambassador_module['liveness_probe'],
             self.default_liveness_probe),
            ("readiness", self.ambassador_module['readiness_probe'],
             self.default_readiness_probe),
            ("diagnostics", self.ambassador_module['diagnostics'],
             self.default_diagnostics)
        ]:
            if cur and cur.get("enabled", False):
                prefix = cur.get("prefix", dflt['prefix'])
                rewrite = cur.get("rewrite", dflt['rewrite'])
                service = cur.get("service", dflt['service'])

                # Push a fake mapping to handle this.
                name = "internal_%s_probe_mapping" % name

                mappings[name] = Mapping(_from=self.ambassador_module,
                                         kind='Mapping',
                                         name=name,
                                         prefix=prefix,
                                         rewrite=rewrite,
                                         service=service)

                self.logger.debug("PROBE %s: %s -> %s%s" %
                                  (name, prefix, service, rewrite))

        # OK! We have all the mappings we need. Process them (don't worry about sorting
        # yet, we'll do that on routes).

        for mapping_name in mappings.keys():
            mapping = mappings[mapping_name]

            # OK. We need a cluster for this service. Derive it from the
            # service name, plus things like circuit breaker and outlier
            # detection settings.
            svc = mapping['service']

            cluster_name_fields = [svc]

            tls_context = mapping.get('tls', None)

            (svc, url, originate_tls,
             otls_name) = self.service_tls_check(svc, tls_context)

            if otls_name:
                cluster_name_fields.append(otls_name)

            cb_name = mapping.get('circuit_breaker', None)

            if cb_name:
                if cb_name in self.breakers:
                    cluster_name_fields.append("cb_%s" % cb_name)
                else:
                    self.logger.error(
                        "CircuitBreaker %s is not defined (mapping %s)" %
                        (cb_name, mapping_name))

            od_name = mapping.get('outlier_detection', None)

            if od_name:
                if od_name in self.outliers:
                    cluster_name_fields.append("od_%s" % od_name)
                else:
                    self.logger.error(
                        "OutlierDetection %s is not defined (mapping %s)" %
                        (od_name, mapping_name))

            cluster_name = 'cluster_%s' % "_".join(cluster_name_fields)
            cluster_name = re.sub(r'[^0-9A-Za-z_]', '_', cluster_name)

            self.logger.debug("%s: svc %s -> cluster %s" %
                              (mapping_name, svc, cluster_name))

            grpc = mapping.get('grpc', False)
            # self.logger.debug("%s has GRPC %s" % (mapping_name, grpc))

            self.add_intermediate_cluster(mapping['_source'],
                                          cluster_name, [url],
                                          cb_name=cb_name,
                                          od_name=od_name,
                                          grpc=grpc,
                                          originate_tls=originate_tls)

            self.add_intermediate_route(mapping['_source'], mapping,
                                        cluster_name)

            # # Also add a diag route.

            # source = mapping['_source']

            # method = mapping.get("method", "GET")
            # dmethod = method.lower()

            # prefix = mapping['prefix']
            # dprefix = prefix[1:] if prefix.startswith('/') else prefix

            # diag_prefix = '/ambassador/v0/diag/%s/%s' % (dmethod, dprefix)
            # diag_rewrite = '/ambassador/v0/diag/%s?method=%s&resource=%s' % (source, method, prefix)

            # self.add_intermediate_route(
            #     '--diagnostics--',
            #     {
            #         'prefix': diag_prefix,
            #         'rewrite': diag_rewrite,
            #         'service': 'cluster_diagnostics'
            #     },
            #     'cluster_diagnostics'
            # )

        # # Also push a fallback diag route, so that one can easily ask for diagnostics
        # # by source file.

        # self.add_intermediate_route(
        #     '--diagnostics--',
        #     {
        #         'prefix': "/ambassador/v0/diag/",
        #         'rewrite': "/ambassador/v0/diag/",
        #         'service': 'cluster_diagnostics'
        #     },
        #     'cluster_diagnostics'
        # )

        # We need to default any unspecified weights and renormalize to 100
        for group_id, route in self.envoy_routes.items():
            clusters = route["clusters"]
            total = 0.0
            unspecified = 0

            for c in clusters:
                if c["weight"] is None:
                    unspecified += 1
                else:
                    total += c["weight"]

            if unspecified:
                for c in clusters:
                    if c["weight"] is None:
                        c["weight"] = (100.0 - total) / unspecified
            elif total != 100.0:
                for c in clusters:
                    c["weight"] *= 100.0 / total

        # OK. When all is said and done, sort the list of routes by descending
        # length of prefix, then prefix itself, then method...
        self.envoy_config['routes'] = sorted(
            [route for group_id, route in self.envoy_routes.items()],
            reverse=True,
            key=Mapping.route_weight)

        # ...then map clusters back into a list...
        self.envoy_config['clusters'] = [
            self.envoy_clusters[name]
            for name in sorted(self.envoy_clusters.keys())
        ]

        # ...and finally repeat for breakers and outliers, but copy them in the process so we
        # can mess with the originals.
        #
        # What's going on here is that circuit-breaker and outlier-detection configs aren't
        # included as independent objects in envoy.json, but we want to be able to discuss
        # them in diag. We also don't need to keep the _source and _referenced_by elements
        # in their real Envoy appearances.

        self.envoy_config['breakers'] = self.clean_and_copy(self.breakers)
        self.envoy_config['outliers'] = self.clean_and_copy(self.outliers)

    def _get_intermediate_for(self, element_list, source_keys, value):
        if not isinstance(value, dict):
            return

        good = True

        if '_source' in value:
            good = False

            value_source = value.get("_source", None)
            value_referenced_by = value.get("_referenced_by", [])

            if ((value_source in source_keys)
                    or (source_keys & set(value_referenced_by))):
                good = True

        if good:
            element_list.append(value)

    def get_intermediate_for(self, source_key):
        source_keys = []

        if source_key in self.source_map:
            # Exact match for a file in the source map: include all the objects
            # in the file.
            source_keys = self.source_map[source_key]
        elif source_key in self.sources:
            # Exact match for an object in a file: include only that object.
            source_keys.append(source_key)
        else:
            # No match at all. Weird.
            return {"error": "No source matches %s" % source_key}

        source_keys = set(source_keys)

        sources = []

        for key in source_keys:
            source_dict = dict(self.sources[key])
            source_dict['errors'] = [{
                'summary':
                error['error'].split('\n', 1)[0],
                'text':
                error['error']
            } for error in self.errors.get(key, [])]

            sources.append(source_dict)

        result = {"sources": sources}

        for key in self.envoy_config.keys():
            result[key] = []

            value = self.envoy_config[key]

            if isinstance(value, list):
                for v2 in value:
                    self._get_intermediate_for(result[key], source_keys, v2)
            else:
                self._get_intermediate_for(result[key], source_keys, value)

        return result

    def generate_envoy_config(self,
                              template=None,
                              template_dir=None,
                              **kwargs):
        # Finally! Render the template to JSON...
        envoy_json = self.to_json(template=template, template_dir=template_dir)

        # We used to use the JSON parser as a final sanity check here. That caused
        # Forge some issues, so it's turned off for now.

        # rc = RichStatus.fromError("impossible")

        # # ...and use the JSON parser as a final sanity check.
        # try:
        #     obj = json.loads(envoy_json)
        #     rc = RichStatus.OK(msg="Envoy configuration OK", envoy_config=obj)
        # except json.decoder.JSONDecodeError as e:
        #     rc = RichStatus.fromError("Invalid Envoy configuration: %s" % str(e),
        #                               raw=envoy_json, exception=e)

        # Go ahead and report that we generated an Envoy config, if we can.
        scout_result = Config.scout_report(action="config",
                                           result=True,
                                           generated=True,
                                           **kwargs)

        rc = RichStatus.OK(envoy_config=envoy_json, scout_result=scout_result)

        # self.logger.debug("Scout reports %s" % json.dumps(rc.scout_result))

        return rc

    def set_config_ambassador(self, module, key, value, merge=False):
        if not merge:
            self.ambassador_module[key] = value
        else:
            self.ambassador_module[key].update(value)

        self.ambassador_module['_source'] = module['_source']

    def update_config_ambassador(self, module, key, value):
        self.set_config_ambassador(module, key, value, merge=True)

    def tls_config_helper(self, name, amod, tmod):
        tmp_config = SourcedDict(_from=amod)
        some_enabled = False

        for context_name in tmod.keys():
            if context_name.startswith('_'):
                continue

            context = tmod[context_name]

            self.logger.debug("context %s -- %s" %
                              (context_name, json.dumps(context)))

            if context.get('enabled', True):
                if context_name == 'server':
                    # Server-side TLS is enabled.
                    self.logger.debug("TLS termination enabled!")
                    some_enabled = True

                    # Switch to port 443 by default...
                    self.set_config_ambassador(amod, 'service_port', 443)

                    # ...and merge in the server-side defaults.
                    tmp_config.update(self.default_tls_config['server'])
                    tmp_config.update(tmod['server'])
                elif context_name == 'client':
                    # Client-side TLS is enabled.
                    self.logger.debug("TLS client certs enabled!")
                    some_enabled = True

                    # Merge in the client-side defaults.
                    tmp_config.update(self.default_tls_config['client'])
                    tmp_config.update(tmod['client'])
                else:
                    # This is a wholly new thing.
                    self.tls_contexts[context_name] = SourcedDict(_from=tmod,
                                                                  **context)

        if some_enabled:
            if 'enabled' in tmp_config:
                del (tmp_config['enabled'])

            # Save the TLS config...
            self.set_config_ambassador(amod, 'tls_config', tmp_config)

        self.logger.debug(
            "TLS config: %s" %
            json.dumps(self.ambassador_module['tls_config'], indent=4))
        self.logger.debug("TLS contexts: %s" % json.dumps(self.tls_contexts))

        return some_enabled

    def module_config_ambassador(self, name, amod, tmod):
        # Toplevel Ambassador configuration. First up, check out TLS.

        have_amod_tls = False

        if amod and ('tls' in amod):
            have_amod_tls = self.tls_config_helper(name, amod, amod['tls'])

        if not have_amod_tls and tmod:
            self.tls_config_helper(name, tmod, tmod)

        # After that, check for port definitions and probes, and copy them in as we find them.
        for key in [
                'service_port', 'admin_port', 'diag_port', 'liveness_probe',
                'readiness_probe'
        ]:
            if amod and (key in amod):
                # Yes. It overrides the default.
                self.set_config_ambassador(amod, key, amod[key])

    def module_config_authentication(self, name, module):
        filter = SourcedDict(_from=module,
                             type="decoder",
                             name="extauth",
                             config={
                                 "cluster": "cluster_ext_auth",
                                 "timeout_ms": 5000
                             })

        path_prefix = module.get("path_prefix", None)

        if path_prefix:
            filter['config']['path_prefix'] = path_prefix

        allowed_headers = module.get("allowed_headers", None)

        if allowed_headers:
            filter['config']['allowed_headers'] = allowed_headers

        self.envoy_config['filters'].insert(0, filter)

        if 'ext_auth_cluster' not in self.envoy_clusters:
            svc = module.get('auth_service', '127.0.0.1:5000')
            tls_context = module.get('tls', None)

            (svc, url, originate_tls,
             otls_name) = self.service_tls_check(svc, tls_context)

            self.add_intermediate_cluster(module['_source'],
                                          'cluster_ext_auth', [url],
                                          type="logical_dns",
                                          lb_type="random",
                                          originate_tls=originate_tls)

    ### DIAGNOSTICS
    def diagnostic_overview(self):
        # Build a set of source _files_ rather than source _objects_.
        source_files = {}

        for filename, source_keys in self.source_map.items():
            self.logger.debug("overview -- filename %s, source_keys %d" %
                              (filename, len(source_keys)))

            # Skip '--internal--' etc.
            if filename.startswith('--'):
                continue

            source_dict = source_files.setdefault(
                filename, {
                    'filename': filename,
                    'objects': {},
                    'count': 0,
                    'plural': "objects",
                    'error_count': 0,
                    'error_plural': "errors"
                })

            for source_key in source_keys:
                self.logger.debug("overview --- source_key %s" % source_key)

                source = self.sources[source_key]
                raw_errors = self.errors.get(source_key, [])

                errors = []

                for error in raw_errors:
                    source_dict['error_count'] += 1

                    errors.append({
                        'summary': error['error'].split('\n', 1)[0],
                        'text': error['error']
                    })

                source_dict['error_plural'] = "error" if (
                    source_dict['error_count'] == 1) else "errors"

                source_dict['count'] += 1
                source_dict['plural'] = "object" if (source_dict['count']
                                                     == 1) else "objects"

                object_dict = source_dict['objects']
                object_dict[source_key] = {
                    'key': source_key,
                    'kind': source['kind'],
                    'errors': errors
                }

        routes = []

        for route in self.envoy_config['routes']:
            if route['_source'] != "--diagnostics--":
                route['group_id'] = Mapping.group_id(
                    route.get('method', 'GET'), route['prefix'],
                    route.get('headers', []))

                routes.append(route)

        configuration = {
            key: self.envoy_config[key]
            for key in self.envoy_config.keys() if key != "routes"
        }

        overview = dict(sources=sorted(source_files.values(),
                                       key=lambda x: x['filename']),
                        routes=routes,
                        **configuration)

        self.logger.debug("overview result %s" %
                          json.dumps(overview, indent=4, sort_keys=True))

        return overview

    def pretty(self, obj, out=sys.stdout):
        out.write(obj)


#        json.dump(obj, out, indent=4, separators=(',',':'), sort_keys=True)
#        out.write("\n")

    def to_json(self, template=None, template_dir=None):
        template_paths = [self.config_dir_path, self.template_dir_path]

        if template_dir:
            template_paths.insert(0, template_dir)

        if not template:
            env = Environment(loader=FileSystemLoader(template_paths))
            template = env.get_template("envoy.j2")

        return (template.render(**self.envoy_config))

    def dump(self):
        print("==== config")
        self.pretty(self.config)

        print("==== envoy_config")
        self.pretty(self.envoy_config)
示例#12
0
文件: scout.py 项目: jianingy/scout
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# filename   : scout.py
# created at : 2013-03-27 20:47:48
# author     : Jianing Yang <jianingy.yang AT gmail DOT com>

__author__ = 'Jianing Yang <jianingy.yang AT gmail DOT com>'

import sys

if __name__ == '__main__':
    from time import sleep
    from scout import Scout

    s = Scout(verbose=True, colorful=True)
    n, last_remain = 0, -1

    s.get_platforms()
    s.inspect()
    remain = len(s.issues)

    while True:
        n = n + 1
        print "INFO: Round %s on platforms: %s" % (n, ",".join(s.platforms))



        remain = len(s.issues)
        if last_remain == remain:
            print "WARN: There's something can be solved automatically."
示例#13
0
class Team(object):
    """A baseball team in a baseball cosmos."""

    def __init__(self, city, league=None):
        """Initialize a Team object."""
        self.cosmos = city.cosmos
        # Attribute the team's league; if None, this means this is an independent
        # club (or something like that)
        self.league = league
        self.league.teams.add(self)
        # Prepare geographic and organizational attributes, which get set by
        # .establish_base_in_city(), a method that will also be called in the case
        # that this franchise relocates
        self.city = None
        self.state = None
        self.country = None
        self.organization = None
        # This gets set by .establish_base_in_city()
        self.nickname = None
        # Prepare attributes that hold team personnel; these will also be updated by
        # .establish_base_in_city()
        self.personnel = set()
        self.owner = None
        self.manager = None
        self.scout = None
        # Prepare a ballpark attribute, which is set by establish_base_in_city() as well
        self.ballpark = None
        # Finally actually establish operations in the city
        self.establish_base_in_city(city=city)
        # Set various attributes
        self.founded = self.cosmos.year
        self.ceased = None
        self.expansion = True if self.cosmos.year > self.league.founded else False
        self.charter_team = True if not self.expansion else False
        # Set history object
        self.history = FranchiseHistory(franchise=self)
        # Prepare various attributes
        self.season = None  # Gets set by TeamSeason.__init__()
        self.defunct = False
        # Assemble a roster of players
        self.players = set()
        self.roster = None
        self._sign_players()
        self._assemble_roster()
        if self.expansion:
            print '{team} have been enfranchised in the {league}.'.format(team=self.name, league=self.league.name)

    def __str__(self):
        """Return string representation."""
        return self.name

    @property
    def name(self):
        """Return the name of this franchise."""
        return "{city} {nickname}".format(city=self.city.name, nickname=self.nickname)

    @property
    def random_player(self):
        """Return a random player on this team."""
        return random.choice(list(self.players))

    def establish_base_in_city(self, city, employees_to_relocate=None):
        """Establish operations in a city, either due to enfranchisement or relocation."""
        # Determine whether this is part of a relocation procedure, which is signaled by
        # employees_to_relocate being passed
        relocating = True if employees_to_relocate else False
        tradition_in_the_old_city = None if not relocating else self.history.tradition
        # Set geographic attributes
        self.city = city
        self.state = city.state
        self.country = city.country
        # Update teams listing of new city
        self.city.teams.add(self)
        # Form a corresponding baseball organization, which will be a Business object that
        # handles business and other considerations
        if not relocating:
            self.organization = BaseballOrganization(team=self)
        else:
            self.organization = RelocatedBaseballOrganization(team=self, employees_to_relocate=employees_to_relocate)
        # Come up with a nickname
        self.nickname = self._determine_nickname(tradition_in_the_old_city=tradition_in_the_old_city)
        # Update the organization's name accordingly
        self.organization.set_name()
        # Set your team personnel
        self.set_team_personnel()
        # Set the team's ballpark, which will have been procured by the organization's
        # __init__() method
        self.ballpark = self.organization.ballpark

    def _determine_nickname(self, tradition_in_the_old_city):
        """Determine a nickname for this team."""
        # If you're relocating, consider retaining the name of the team
        if self._decide_to_retain_nickname(tradition_in_the_old_city=tradition_in_the_old_city):
            return self.nickname
        else:
            return self._come_up_with_nickname()

    def _decide_to_retain_nickname(self, tradition_in_the_old_city):
        """Decide whether to retain the nickname of this relocating franchise."""
        if tradition_in_the_old_city:  # This signals relocation
            chance_we_retain_name = self.cosmos.config.chance_a_relocated_team_retains_name(
                tradition_in_the_old_city=tradition_in_the_old_city
            )
            if random.random() < chance_we_retain_name:
                # Make sure the name is not already taken
                if not any(t for t in self.city.teams if t.nickname == self.nickname):
                    return True
        return False

    def _come_up_with_nickname(self):
        """Come up with a nickname for this team."""
        # TODO CITY APT NICKNAMES AND NAMES OF HISTORICAL TEAMS IN THE CITY
        name_already_taken = True
        nickname = None
        while name_already_taken:
            nickname = Names.a_baseball_team_nickname(year=self.city.cosmos.year)
            # Make sure the name is not already taken
            if not any(t for t in self.city.teams if t.nickname == nickname):
                name_already_taken = False
            else:  # TODO fix this duct tape here
                return "Generics"
        return nickname

    def set_team_personnel(self):
        """Set the personnel working for this team.

        In this method, we set attributes pertaining to the actual baseball-class objects
        corresponding to the employees of this organization. This method may be called any
        time an employee in the organization quits, retires, is fired, or dies.
        """
        # Set team owner
        owner_person = next(e for e in self.organization.employees if isinstance(e, BaseballTeamOwner)).person
        self.owner = Owner(person=owner_person) if not owner_person.team_owner else owner_person.team_owner
        # Set manager
        manager_person = next(e for e in self.organization.employees if isinstance(e, BaseballManager)).person
        self.manager = (
            Manager(person=manager_person, team=self) if not manager_person.manager else manager_person.manager
        )
        # Set scout
        scout_person = next(e for e in self.organization.employees if isinstance(e, BaseballScout)).person
        self.scout = Scout(person=scout_person, team=self) if not scout_person.scout else scout_person.scout
        # Set personnel attribute
        self.personnel = {self.owner, self.manager, self.scout}
        for p in self.personnel:
            p.team = self

    def _sign_players(self):
        """Sign players until you have a full roster."""
        roster_limit = self.league.classification.roster_limit
        if len(self.players) < roster_limit:
            print "\t{scout} is signing players...".format(scout=self.scout.person.name)
        while len(self.players) < roster_limit:
            position_of_need = self.manager.decide_position_of_greatest_need()
            secured_player = self.scout.secure_a_player(position=position_of_need)
            self._sign_player(player=secured_player, position=position_of_need)
        # Update the team's roster
        self._assemble_roster()

    def _sign_player(self, player, position):
        """Sign the given player to play at the given position."""
        print "\t\tsigning {}...".format(player.person.name)
        player.career.team = self
        player.position = position
        self.players.add(player)
        # Actually hire the player as an employee in the organization
        self.organization.hire(occupation_of_need=BaseballPlayer, shift="special", selected_candidate=player.person)

    def _assemble_roster(self):
        """Assemble a roster for this team."""
        # Prepare some variables that we'll need
        roster_order = ('P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF')
        lineup = []
        available_players = list(self.players)
        # Assemble starters
        for position_to_slot in roster_order:
            available_players_at_that_position = [p for p in available_players if p.position == position_to_slot]
            best_available_at_that_position = max(
                available_players_at_that_position,
                key=lambda player: self.scout.grade(prospect=player, position=position_to_slot)
            )
            lineup.append(best_available_at_that_position)
            available_players.remove(best_available_at_that_position)
        # Assemble bullpen and bench
        bullpen = []
        bench = []
        for bench_player in available_players:
            if bench_player.position == 'P':
                bullpen.append(bench_player)
            else:
                bench.append(bench_player)
        # Instantiate and set a Roster object
        self.roster = Roster(lineup=tuple(lineup), bullpen=tuple(bullpen), bench=tuple(bench))

    def process_a_retirement(self, player):
        """Handle the retirement of a player."""
        # TODO DETERMINE CEREMONIES, ETC., IF EXCEPTIONAL CAREER
        self._terminate_contract(player=player)
        self.history.former_players.add(player)
        # Let the league know
        self.league.process_a_retirement(player=player)

    def _terminate_contract(self, player):
        """Terminate the contract of a player."""
        self.players.remove(player)
        player.career.team = None
        # If this is during a season, potentially sign a replacement
        if self.season:
            self._sign_players()

    def conduct_offseason_activity(self):
        """Conduct this team's offseason procedures."""
        config = self.cosmos.config
        # If you're a fairly established team in a very established league, consider relocating;
        # newer teams won't consider relocating, since they're still establishing a fan base in
        # their current cities; teams in leagues that aren't established don't relocate because
        # they would have no more value to another city than an expansion team would
        team_is_established_in_town = (
            self.history.number_of_years_in_town >
            config.minimum_number_of_years_in_city_before_considering_relocation(year=self.cosmos.year)
        )
        league_is_established_enough_for_relocation = (
            self.league.history.years_in_existence >
            config.minimum_number_of_years_before_league_established_enough_for_team_relocation()
        )
        if team_is_established_in_town and league_is_established_enough_for_relocation:
            self._potentially_relocate()
        # Otherwise, if you aren't a brand new team and the league isn't established yet,
        # then consider folding
        elif self.history.seasons and not league_is_established_enough_for_relocation:
            self._potentially_fold()

    def _potentially_fold(self):
        """Potentially _fold this franchise."""
        chance_of_folding = self.cosmos.config.chance_of_folding(
            franchise_winning_percentage=self.history.cumulative_winning_percentage,
            league_years_in_existence=self.league.history.years_in_existence
        )
        if random.random() < chance_of_folding:
            self._fold()

    def _fold(self):
        """Cease operations of this franchise."""
        # Have the organization go out of business, which will officially terminate
        # all of the organization's employee occupations -- have to do this first so
        # that the BaseballFranchiseTermination object gets all the attributes it needs
        self.organization.go_out_of_business(reason=BaseballFranchiseTermination(franchise=self))
        # Sever ties with the city you're located in
        self._sever_ties_with_city()
        # Sever ties with the league you're in
        self._sever_ties_with_league()
        # Sever ties with players and personnel
        self._sever_ties_with_players_and_personnel()
        # Update attributes
        self.defunct = True
        self.ceased = self.cosmos.year

    def _sever_ties_with_city(self):
        """Sever ties with the city you were located in."""
        self.city.teams.remove(self)
        self.city.former_teams.add(self)

    def _sever_ties_with_league(self):
        """Sever ties with the league you were a member of."""
        self.league.teams.remove(self)
        self.league.history.defunct_teams.add(self)

    def _sever_ties_with_players_and_personnel(self):
        """Sever ties with your players and personnel."""
        for stakeholder in self.players | self.personnel:
            stakeholder.career.team = None

    def _potentially_relocate(self):
        """Potentially _relocate this franchise to a new city."""
        config = self.cosmos.config
        if self._qualified_to_relocate():
            if random.random() < config.chance_a_team_that_qualifies_to_relocate_will_relocate:
                self._relocate()

    def _qualified_to_relocate(self):
        """Return whether this franchise is qualified to relocate."""
        config = self.cosmos.config
        # If your last season was a losing season...
        last_season_was_a_losing_season = self.history.seasons[-1].winning_percentage < 0.5
        if not last_season_was_a_losing_season:
            return False
        # ...and your franchise isn't too storied to ever relocate...
        franchise_is_too_storied_to_relocate = config.franchise_too_storied_to_relocate(
            tradition=self.history.tradition
        )
        if franchise_is_too_storied_to_relocate:
            return False
        # ...and you've averaged a losing season over the duration of your fan base's memory...
        fan_base_memory_window = int(config.fan_base_memory_window())
        beginning_of_that_window = self.cosmos.year-fan_base_memory_window
        winning_percentage_during_fan_base_memory = self.history.winning_percentage_during_window(
            start_year=beginning_of_that_window, end_year=self.cosmos.year, city=self.city
        )
        averaged_losing_season_during_fan_base_memory = winning_percentage_during_fan_base_memory < 0.5
        if not averaged_losing_season_during_fan_base_memory:
            return False
        # ..then you are qualified to relocate
        return True

    def _relocate(self):
        """Relocate this franchise to a new city."""
        # TODO EMBITTER FANS IN THIS CITY
        # Sever ties with the city you are departing
        self._sever_ties_with_city()
        # Decide where to relocate to
        city_to_relocate_to = self._decide_where_to_relocate_to()
        # Shut down the current organization, but save all its employees so
        # that we can offer them the chance to relocate to the new organization
        employees_to_relocate = set(self.organization.employees)
        # Set up operations in the new city
        self.establish_base_in_city(city=city_to_relocate_to, employees_to_relocate=employees_to_relocate)
        print "The {old_name} of the {league} have relocated to become the {new_name}".format(
            # old_name="{} {}".format(self.history.seasons[-1].city.name, self.history.seasons[-1].nickname),
            old_name='LOL HI!',
            league=self.league.name,
            new_name=self.name
        )

    def _decide_where_to_relocate_to(self):
        """Decide which city to relocate this franchise to."""
        cities_ranked_by_utility = self.league.rank_prospective_cities()
        cities_ranked_by_utility.remove(self.city)  # Throw out the city we're leaving
        # If the hometown of the owner of this franchise is a viable city to relocate
        # to, then select that city
        if self.owner.person.hometown in cities_ranked_by_utility:
            return self.owner.person.hometown
        most_appealing_city = cities_ranked_by_utility[0]
        return most_appealing_city

    def operate(self):
        """Conduct the regular operations of this franchise."""
        # TODO MAKE TRAVEL REALISTIC ONCE TRAVEL SYSTEM IMPLEMENTED
        if self.season:
            # Check if you have any games scheduled for this timestep
            try:
                next_scheduled_series = self.season.schedule.next_series
                ordinal_date_of_next_game, timestep_of_next_game = next_scheduled_series.dates_scheduled[0]
                ballpark_of_next_game = next_scheduled_series.home_team.ballpark
                game_is_this_timestep = (
                    ordinal_date_of_next_game == self.cosmos.ordinal_date and
                    timestep_of_next_game == self.cosmos.time_of_day
                )
                # If the game is this timestep...
                if game_is_this_timestep:
                    # ...then head to the ballpark...
                    for stakeholder in {self.manager, self.scout} | self.players:
                        stakeholder.person.go_to(destination=ballpark_of_next_game, occasion="baseball")
                    # ...and let the league know the game is this timestep; League.operate() will
                    # eventually instantiate the actual Game object
                    self.league.add_to_game_queue(next_scheduled_series)
            # No game scheduled for this timestep, so just hang out
            except AttributeError:
                pass
        else:  # Off-season
            # Players will have already retired by LeagueSeason.review() calling
            # TeamSeason.review(); if anyone did retire, this team needs to sign
            # more players to get to the roster limit
            self._sign_players()
示例#14
0
文件: core.py 项目: hbouvier/forge
    def setup(self):
        with task.verbose(True):
            scout = Scout("forge", __version__)
            scout_res = scout.report()

            task.echo(self.terminal.bold("== Checking Kubernetes Setup =="))
            task.echo()

            checks = (("kubectl", "version", "--short"),
                      ("kubectl", "get", "service", "kubernetes",
                       "--namespace", "default"))

            for cmd in checks:
                e = sh.run(*cmd)
                if e.result is ERROR:
                    task.echo()
                    task.echo(
                        self.terminal.bold_red(
                            "== Kubernetes Check Failed =="))
                    task.echo()
                    task.echo()
                    task.echo(
                        self.terminal.bold(
                            "Please make sure kubectl is installed/configured correctly."
                        ))
                    raise TaskError("")

            regtype = "generic"
            prompts = {
                ("generic", "url"):
                ("Docker registry url", "registry.hub.docker.com"),
                ("generic", "user"): ("Docker user", None),
                ("generic", "namespace"):
                ("Docker namespace/organization", None),
                ("generic", "password"): ("Docker password", None),
                ("gcr", "key"): ["Path to json key", None]
            }

            @task()
            def validate():
                c = yaml.dump({"registry": regvalues})
                task.echo(c)
                conf = config.load("setup", c)
                dr = get_docker(conf.registry)
                dr.validate()

            task.echo()
            task.echo(self.terminal.bold("== Setting up Docker =="))

            while True:
                task.echo()
                types = OrderedDict((("ecr", config.ECR), ("gcr", config.GCR),
                                     ("generic", config.DOCKER)))
                regtype = self.prompt(
                    "Registry type (one of %s)" % ", ".join(types.keys()),
                    regtype)
                if regtype not in types:
                    task.echo()
                    task.echo(
                        self.terminal.red(
                            "%s is not a valid choice, please choose one of %s"
                            % (regtype, ", ".join(types.keys()))))
                    task.echo()
                    regtype = "generic"
                    continue

                reg = types[regtype]
                regvalues = OrderedDict(
                    (("type", reg.fields["type"].type.value), ))
                for f in reg.fields.values():
                    if f.name == "type": continue
                    prompt, default = prompts.get((regtype, f.name),
                                                  (f.name, None))
                    if (regtype, f.name) == ("gcr", "key"):
                        key, value = self.prompt(prompt,
                                                 default,
                                                 loader=file_contents)
                        prompts[(regtype, f.name)][1] = key
                    else:
                        if f.name in ("password", ):
                            value = self.prompt(prompt, default, echo=False)
                        else:
                            value = self.prompt(prompt,
                                                default,
                                                optional=not f.required)
                    if f.name in ("password", "key"):
                        regvalues[f.name] = base64.encodestring(value)
                    else:
                        regvalues[f.name] = value

                task.echo()
                e = validate.run()
                if e.result is ERROR:
                    task.echo()
                    task.echo(self.terminal.red("-- please try again --"))
                    e.recover()
                    continue
                else:
                    break

            task.echo()

            config_content = renders("SETUP_TEMPLATE",
                                     SETUP_TEMPLATE,
                                     yaml=yaml.dump({"registry": regvalues},
                                                    allow_unicode=True,
                                                    default_flow_style=False))

            config_file = "forge.yaml"

            task.echo(
                self.terminal.bold("== Writing config to %s ==" % config_file))

            with open(config_file, "write") as fd:
                fd.write(config_content)

            task.echo()
            task.echo(config_content.strip())
            task.echo()

            task.echo(self.terminal.bold("== Done =="))
示例#15
0
# # Print an ASCII map
# world.print_rooms()

player = Player(world.starting_room)

# Fill this out with directions to walk
# traversal_path = ['n', 'n']
traversal_path = []

# Generate permutations of directional preferences to iterate over:
compasses = itertools.permutations(["n", "e", "s", "w"])
# Scout objects have methods to explore multiple path options
scouts = []
for compass in compasses:
    # For each directional permutation, test both chirality options:
    scouts.append(Scout(world.starting_room, world, True, list(compass)))
    scouts.append(Scout(world.starting_room, world, False, list(compass)))
for scout in scouts:
    # Alternate exploration modes until maze has been traversed:
    while len(scout.visited) < len(room_graph):
        # Transit continguous unexplored area:
        scout.automap()
        # Move to nearest room with unexplored neighbor:
        scout.go_to_new()

# Sort paths generated by all Scout objects in order of length
traversals = sorted([scout.steps for scout in scouts], key=lambda traversal: len(traversal))
# Save shortest path to be sent to Player object
traversal_path = traversals[0]

# TRAVERSAL TEST - DO NOT MODIFY
示例#16
0
文件: cli.py 项目: bogdanap/forge
    def setup(self):
        scout = Scout("forge", __version__)
        scout_res = scout.report()

        print self.terminal.bold("== Checking Kubernetes Setup ==")
        print

        checks = (("kubectl", "version", "--short"),
                  ("kubectl", "get", "service", "kubernetes", "--namespace",
                   "default"))

        for cmd in checks:
            e = sh.run(*cmd)
            if e.result is ERROR:
                print
                raise CLIError(
                    self.terminal.red("== Kubernetes Check Failed ==") +
                    "\n\nPlease make sure kubectl is installed/configured correctly."
                )

        registry = "registry.hub.docker.com"
        repo = None
        user = os.environ.get("USER", "")
        password = None
        json_key = None

        @task()
        def validate():
            dr = Docker(registry, repo, user, password)
            dr.validate()

        print
        print self.terminal.bold("== Setting up Docker ==")

        while True:
            print
            registry = self.prompt("Docker registry", registry)
            user = self.prompt("Docker user", user)
            repo = self.prompt("Docker organization", user)
            if user == "_json_key":
                json_key, password = self.prompt("Path to json key",
                                                 json_key,
                                                 loader=file_contents)
            else:
                password = self.prompt("Docker password", echo=False)

            print
            e = validate.run(
                task_include=lambda x: x.task.name in ('pull', 'push', 'tag'))
            if e.result is ERROR:
                print
                print self.terminal.red("-- please try again --")
                continue
            else:
                break

        print

        config = renders("SETUP_TEMPLATE",
                         SETUP_TEMPLATE,
                         docker="%s/%s" % (registry, repo),
                         user=user,
                         password=base64.encodestring(password).replace(
                             "\n", "\n  "))

        config_file = "forge.yaml"

        print self.terminal.bold("== Writing config to %s ==" % config_file)

        with open(config_file, "write") as fd:
            fd.write(config)

        print
        print config.strip()
        print

        print self.terminal.bold("== Done ==")
示例#17
0
    scout_version = "%s-%s+%s" % (v, p, b)

logger.debug("Scout version %s" % scout_version)

scout = None

runtime = "kubernetes" if os.environ.get('KUBERNETES_SERVICE_HOST',
                                         None) else "docker"
logger.debug("runtime: %s" % runtime)

try:
    namespace = os.environ.get('AMBASSADOR_NAMESPACE', 'default')

    scout = Scout(app="ambassador",
                  version=scout_version,
                  id_plugin=Scout.configmap_install_id_plugin,
                  id_plugin_args={"namespace": namespace})
except OSError as e:
    logger.warning("couldn't do version check: %s" % str(e))


def handle_exception(what, e, **kwargs):
    tb = "\n".join(traceback.format_exception(*sys.exc_info()))

    if scout:
        result = scout.report(action=what,
                              exception=str(e),
                              traceback=tb,
                              runtime=runtime,
                              **kwargs)
        logger.debug("Scout %s, result: %s" %
示例#18
0
# Configuration
# ----------------------------------------------------------------------------------------------------------------------

config_root = Path.home() / ".config" / PROGRAM_NAME
config_root.mkdir(parents=True, exist_ok=True)
config_file = config_root / 'config.json'

with config_file.open('a+', encoding='utf-8') as f:
    f.seek(0)
    data = f.read() or '{}'
    config = json.loads(data)

kubeconfig_root = Path.home() / ".kube"
kubeconfig_root.mkdir(exist_ok=True)

scout = Scout(PROGRAM_NAME, __version__)
scout_resp = scout.report()

# ----------------------------------------------------------------------------------------------------------------------
# Utility Functions
# ----------------------------------------------------------------------------------------------------------------------


def create_kubeconfig_var_message(path):
    msg = """Set your KUBECONFIG environment variable to use kubectl"""

    shell = os.getenv("SHELL", "").lower()
    if "/bash" in shell or "/zsh" in shell:
        msg += """
        token
        export KUBECONFIG={0}
示例#19
0
parser = argparse.ArgumentParser(
    prog="DangerBot",
    description="A script that plays Urban Dead in order to update the wiki")

parser.add_argument(
    "command",
    choices=['scout', 'report'],
    help=
    "scout determines the state of the world; report uses the logs to update the wiki"
)
parser.add_argument("character_name",
                    help="used for 'scout', the name of the character to move")

args = parser.parse_args()

if args.command == "scout":
    if args.character_name is None:
        raise Exception(
            "Must provide a character name when using the scout command")

    print("Moving the character")
    s = Scout(args.character_name)
    s.scout()

elif args.command == "report":
    print("Updating the wiki")

else:
    print("Unknown command {}".format(args.command))
示例#20
0
    def body(self):
        """
        The leader starts by spawning a scout for its initial ballot
        number, and then enters into a loop awaiting messages. There
        are three types of messages that cause transitions:

        - Propose: A replica proposes given command for given slot number

        - Adopted: Sent by a scout, this message signifies that the
        current ballot number has been adopted by a majority of
        acceptors. (If an adopted message arrives for an old ballot
        number, it is ignored.) The set pvalues contains all pvalues
        accepted by these acceptors prior to the adopted ballot
        number.

        - Preempted: Sent by either a scout or a commander, it means
        that some acceptor has adopted the ballot number that is
        included in the message. If this ballot number is higher than
        the current ballot number of the leader, it may no longer be
        possible to use the current ballot number to choose a command.
        """
        print "Here I am: ", self.id
        Scout(self.env,
              "scout:%s:%s" % (str(self.id), str(self.ballot_number)), self.id,
              self.config.acceptors, self.ballot_number)
        while True:
            msg = self.getNextMessage()
            if isinstance(msg, ProposeMessage):
                if msg.slot_number not in self.proposals:
                    self.proposals[msg.slot_number] = msg.command
                    if self.active:
                        Commander(
                            self.env, "commander:%s:%s:%s" %
                            (str(self.id), str(self.ballot_number),
                             str(msg.slot_number)), self.id,
                            self.config.acceptors, self.config.replicas,
                            self.ballot_number, msg.slot_number, msg.command)
            elif isinstance(msg, AdoptedMessage):
                # Decrease timeout since the leader does not seem to
                # be competing with another leader.
                if self.timeout > TIMEOUTSUBTRACT:
                    self.timeout = self.timeout - TIMEOUTSUBTRACT
                    print self.id, "Timeout decreased: ", self.timeout
                if self.ballot_number == msg.ballot_number:
                    pmax = {}
                    # For every slot number add the proposal with
                    # the highest ballot number to proposals
                    for pv in msg.accepted:
                        if pv.slot_number not in pmax or \
                              pmax[pv.slot_number] < pv.ballot_number:
                            pmax[pv.slot_number] = pv.ballot_number
                            self.proposals[pv.slot_number] = pv.command
                    # Start a commander (i.e. run Phase 2) for every
                    # proposal (from the beginning)
                    for sn in self.proposals:
                        Commander(
                            self.env, "commander:%s:%s:%s" %
                            (str(self.id), str(self.ballot_number), str(sn)),
                            self.id, self.config.acceptors,
                            self.config.replicas, self.ballot_number, sn,
                            self.proposals.get(sn))
                    self.active = True
            elif isinstance(msg, PreemptedMessage):
                # The leader is competing with another leader
                if msg.ballot_number.leader_id > self.id:
                    # Increase timeout because the other leader has priority
                    self.timeout = self.timeout * TIMEOUTMULTIPLY
                    print self.id, "Timeout increased: ", self.timeout
                if msg.ballot_number > self.ballot_number:
                    self.active = False
                    self.ballot_number = BallotNumber(
                        msg.ballot_number.round + 1, self.id)
                    Scout(
                        self.env, "scout:%s:%s" %
                        (str(self.id), str(self.ballot_number)), self.id,
                        self.config.acceptors, self.ballot_number)

            else:
                print "Leader: unknown msg type"
            sleep(self.timeout)
示例#21
0
class Team(object):
    """A baseball team in a baseball cosmos."""
    def __init__(self, city, league=None):
        """Initialize a Team object."""
        self.cosmos = city.cosmos
        # Attribute the team's league; if None, this means this is an independent
        # club (or something like that)
        self.league = league
        self.league.teams.add(self)
        # Prepare geographic and organizational attributes, which get set by
        # .establish_base_in_city(), a method that will also be called in the case
        # that this franchise relocates
        self.city = None
        self.state = None
        self.country = None
        self.organization = None
        # This gets set by .establish_base_in_city()
        self.nickname = None
        # Prepare attributes that hold team personnel; these will also be updated by
        # .establish_base_in_city()
        self.personnel = set()
        self.owner = None
        self.manager = None
        self.scout = None
        # Prepare a ballpark attribute, which is set by establish_base_in_city() as well
        self.ballpark = None
        # Finally actually establish operations in the city
        self.establish_base_in_city(city=city)
        # Set various attributes
        self.founded = self.cosmos.year
        self.ceased = None
        self.expansion = True if self.cosmos.year > self.league.founded else False
        self.charter_team = True if not self.expansion else False
        # Set history object
        self.history = FranchiseHistory(franchise=self)
        # Prepare various attributes
        self.season = None  # Gets set by TeamSeason.__init__()
        self.defunct = False
        # Assemble a roster of players
        self.players = set()
        self.roster = None
        self._sign_players()
        self._assemble_roster()
        if self.expansion:
            print '{team} have been enfranchised in the {league}.'.format(
                team=self.name, league=self.league.name)

    def __str__(self):
        """Return string representation."""
        return self.name

    @property
    def name(self):
        """Return the name of this franchise."""
        return "{city} {nickname}".format(city=self.city.name,
                                          nickname=self.nickname)

    @property
    def random_player(self):
        """Return a random player on this team."""
        return random.choice(list(self.players))

    def establish_base_in_city(self, city, employees_to_relocate=None):
        """Establish operations in a city, either due to enfranchisement or relocation."""
        # Determine whether this is part of a relocation procedure, which is signaled by
        # employees_to_relocate being passed
        relocating = True if employees_to_relocate else False
        tradition_in_the_old_city = None if not relocating else self.history.tradition
        # Set geographic attributes
        self.city = city
        self.state = city.state
        self.country = city.country
        # Update teams listing of new city
        self.city.teams.add(self)
        # Form a corresponding baseball organization, which will be a Business object that
        # handles business and other considerations
        if not relocating:
            self.organization = BaseballOrganization(team=self)
        else:
            self.organization = RelocatedBaseballOrganization(
                team=self, employees_to_relocate=employees_to_relocate)
        # Come up with a nickname
        self.nickname = self._determine_nickname(
            tradition_in_the_old_city=tradition_in_the_old_city)
        # Update the organization's name accordingly
        self.organization.set_name()
        # Set your team personnel
        self.set_team_personnel()
        # Set the team's ballpark, which will have been procured by the organization's
        # __init__() method
        self.ballpark = self.organization.ballpark

    def _determine_nickname(self, tradition_in_the_old_city):
        """Determine a nickname for this team."""
        # If you're relocating, consider retaining the name of the team
        if self._decide_to_retain_nickname(
                tradition_in_the_old_city=tradition_in_the_old_city):
            return self.nickname
        else:
            return self._come_up_with_nickname()

    def _decide_to_retain_nickname(self, tradition_in_the_old_city):
        """Decide whether to retain the nickname of this relocating franchise."""
        if tradition_in_the_old_city:  # This signals relocation
            chance_we_retain_name = self.cosmos.config.chance_a_relocated_team_retains_name(
                tradition_in_the_old_city=tradition_in_the_old_city)
            if random.random() < chance_we_retain_name:
                # Make sure the name is not already taken
                if not any(t for t in self.city.teams
                           if t.nickname == self.nickname):
                    return True
        return False

    def _come_up_with_nickname(self):
        """Come up with a nickname for this team."""
        # TODO CITY APT NICKNAMES AND NAMES OF HISTORICAL TEAMS IN THE CITY
        name_already_taken = True
        nickname = None
        while name_already_taken:
            nickname = Names.a_baseball_team_nickname(
                year=self.city.cosmos.year)
            # Make sure the name is not already taken
            if not any(t for t in self.city.teams if t.nickname == nickname):
                name_already_taken = False
            else:  # TODO fix this duct tape here
                return "Generics"
        return nickname

    def set_team_personnel(self):
        """Set the personnel working for this team.

        In this method, we set attributes pertaining to the actual baseball-class objects
        corresponding to the employees of this organization. This method may be called any
        time an employee in the organization quits, retires, is fired, or dies.
        """
        # Set team owner
        owner_person = next(e for e in self.organization.employees
                            if isinstance(e, BaseballTeamOwner)).person
        self.owner = Owner(
            person=owner_person
        ) if not owner_person.team_owner else owner_person.team_owner
        # Set manager
        manager_person = next(e for e in self.organization.employees
                              if isinstance(e, BaseballManager)).person
        self.manager = (Manager(person=manager_person, team=self) if
                        not manager_person.manager else manager_person.manager)
        # Set scout
        scout_person = next(e for e in self.organization.employees
                            if isinstance(e, BaseballScout)).person
        self.scout = Scout(
            person=scout_person,
            team=self) if not scout_person.scout else scout_person.scout
        # Set personnel attribute
        self.personnel = {self.owner, self.manager, self.scout}
        for p in self.personnel:
            p.team = self

    def _sign_players(self):
        """Sign players until you have a full roster."""
        roster_limit = self.league.classification.roster_limit
        if len(self.players) < roster_limit:
            print "\t{scout} is signing players...".format(
                scout=self.scout.person.name)
        while len(self.players) < roster_limit:
            position_of_need = self.manager.decide_position_of_greatest_need()
            secured_player = self.scout.secure_a_player(
                position=position_of_need)
            self._sign_player(player=secured_player, position=position_of_need)
        # Update the team's roster
        self._assemble_roster()

    def _sign_player(self, player, position):
        """Sign the given player to play at the given position."""
        print "\t\tsigning {}...".format(player.person.name)
        player.career.team = self
        player.position = position
        self.players.add(player)
        # Actually hire the player as an employee in the organization
        self.organization.hire(occupation_of_need=BaseballPlayer,
                               shift="special",
                               selected_candidate=player.person)

    def _assemble_roster(self):
        """Assemble a roster for this team."""
        # Prepare some variables that we'll need
        roster_order = ('P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF')
        lineup = []
        available_players = list(self.players)
        # Assemble starters
        for position_to_slot in roster_order:
            available_players_at_that_position = [
                p for p in available_players if p.position == position_to_slot
            ]
            best_available_at_that_position = max(
                available_players_at_that_position,
                key=lambda player: self.scout.grade(prospect=player,
                                                    position=position_to_slot))
            lineup.append(best_available_at_that_position)
            available_players.remove(best_available_at_that_position)
        # Assemble bullpen and bench
        bullpen = []
        bench = []
        for bench_player in available_players:
            if bench_player.position == 'P':
                bullpen.append(bench_player)
            else:
                bench.append(bench_player)
        # Instantiate and set a Roster object
        self.roster = Roster(lineup=tuple(lineup),
                             bullpen=tuple(bullpen),
                             bench=tuple(bench))

    def process_a_retirement(self, player):
        """Handle the retirement of a player."""
        # TODO DETERMINE CEREMONIES, ETC., IF EXCEPTIONAL CAREER
        self._terminate_contract(player=player)
        self.history.former_players.add(player)
        # Let the league know
        self.league.process_a_retirement(player=player)

    def _terminate_contract(self, player):
        """Terminate the contract of a player."""
        self.players.remove(player)
        player.career.team = None
        # If this is during a season, potentially sign a replacement
        if self.season:
            self._sign_players()

    def conduct_offseason_activity(self):
        """Conduct this team's offseason procedures."""
        config = self.cosmos.config
        # If you're a fairly established team in a very established league, consider relocating;
        # newer teams won't consider relocating, since they're still establishing a fan base in
        # their current cities; teams in leagues that aren't established don't relocate because
        # they would have no more value to another city than an expansion team would
        team_is_established_in_town = (
            self.history.number_of_years_in_town > config.
            minimum_number_of_years_in_city_before_considering_relocation(
                year=self.cosmos.year))
        league_is_established_enough_for_relocation = (
            self.league.history.years_in_existence > config.
            minimum_number_of_years_before_league_established_enough_for_team_relocation(
            ))
        if team_is_established_in_town and league_is_established_enough_for_relocation:
            self._potentially_relocate()
        # Otherwise, if you aren't a brand new team and the league isn't established yet,
        # then consider folding
        elif self.history.seasons and not league_is_established_enough_for_relocation:
            self._potentially_fold()

    def _potentially_fold(self):
        """Potentially _fold this franchise."""
        chance_of_folding = self.cosmos.config.chance_of_folding(
            franchise_winning_percentage=self.history.
            cumulative_winning_percentage,
            league_years_in_existence=self.league.history.years_in_existence)
        if random.random() < chance_of_folding:
            self._fold()

    def _fold(self):
        """Cease operations of this franchise."""
        # Have the organization go out of business, which will officially terminate
        # all of the organization's employee occupations -- have to do this first so
        # that the BaseballFranchiseTermination object gets all the attributes it needs
        self.organization.go_out_of_business(
            reason=BaseballFranchiseTermination(franchise=self))
        # Sever ties with the city you're located in
        self._sever_ties_with_city()
        # Sever ties with the league you're in
        self._sever_ties_with_league()
        # Sever ties with players and personnel
        self._sever_ties_with_players_and_personnel()
        # Update attributes
        self.defunct = True
        self.ceased = self.cosmos.year

    def _sever_ties_with_city(self):
        """Sever ties with the city you were located in."""
        self.city.teams.remove(self)
        self.city.former_teams.add(self)

    def _sever_ties_with_league(self):
        """Sever ties with the league you were a member of."""
        self.league.teams.remove(self)
        self.league.history.defunct_teams.add(self)

    def _sever_ties_with_players_and_personnel(self):
        """Sever ties with your players and personnel."""
        for stakeholder in self.players | self.personnel:
            stakeholder.career.team = None

    def _potentially_relocate(self):
        """Potentially _relocate this franchise to a new city."""
        config = self.cosmos.config
        if self._qualified_to_relocate():
            if random.random(
            ) < config.chance_a_team_that_qualifies_to_relocate_will_relocate:
                self._relocate()

    def _qualified_to_relocate(self):
        """Return whether this franchise is qualified to relocate."""
        config = self.cosmos.config
        # If your last season was a losing season...
        last_season_was_a_losing_season = self.history.seasons[
            -1].winning_percentage < 0.5
        if not last_season_was_a_losing_season:
            return False
        # ...and your franchise isn't too storied to ever relocate...
        franchise_is_too_storied_to_relocate = config.franchise_too_storied_to_relocate(
            tradition=self.history.tradition)
        if franchise_is_too_storied_to_relocate:
            return False
        # ...and you've averaged a losing season over the duration of your fan base's memory...
        fan_base_memory_window = int(config.fan_base_memory_window())
        beginning_of_that_window = self.cosmos.year - fan_base_memory_window
        winning_percentage_during_fan_base_memory = self.history.winning_percentage_during_window(
            start_year=beginning_of_that_window,
            end_year=self.cosmos.year,
            city=self.city)
        averaged_losing_season_during_fan_base_memory = winning_percentage_during_fan_base_memory < 0.5
        if not averaged_losing_season_during_fan_base_memory:
            return False
        # ..then you are qualified to relocate
        return True

    def _relocate(self):
        """Relocate this franchise to a new city."""
        # TODO EMBITTER FANS IN THIS CITY
        # Sever ties with the city you are departing
        self._sever_ties_with_city()
        # Decide where to relocate to
        city_to_relocate_to = self._decide_where_to_relocate_to()
        # Shut down the current organization, but save all its employees so
        # that we can offer them the chance to relocate to the new organization
        employees_to_relocate = set(self.organization.employees)
        # Set up operations in the new city
        self.establish_base_in_city(
            city=city_to_relocate_to,
            employees_to_relocate=employees_to_relocate)
        print "The {old_name} of the {league} have relocated to become the {new_name}".format(
            # old_name="{} {}".format(self.history.seasons[-1].city.name, self.history.seasons[-1].nickname),
            old_name='LOL HI!',
            league=self.league.name,
            new_name=self.name)

    def _decide_where_to_relocate_to(self):
        """Decide which city to relocate this franchise to."""
        cities_ranked_by_utility = self.league.rank_prospective_cities()
        cities_ranked_by_utility.remove(
            self.city)  # Throw out the city we're leaving
        # If the hometown of the owner of this franchise is a viable city to relocate
        # to, then select that city
        if self.owner.person.hometown in cities_ranked_by_utility:
            return self.owner.person.hometown
        most_appealing_city = cities_ranked_by_utility[0]
        return most_appealing_city

    def operate(self):
        """Conduct the regular operations of this franchise."""
        # TODO MAKE TRAVEL REALISTIC ONCE TRAVEL SYSTEM IMPLEMENTED
        if self.season:
            # Check if you have any games scheduled for this timestep
            try:
                next_scheduled_series = self.season.schedule.next_series
                ordinal_date_of_next_game, timestep_of_next_game = next_scheduled_series.dates_scheduled[
                    0]
                ballpark_of_next_game = next_scheduled_series.home_team.ballpark
                game_is_this_timestep = (
                    ordinal_date_of_next_game == self.cosmos.ordinal_date
                    and timestep_of_next_game == self.cosmos.time_of_day)
                # If the game is this timestep...
                if game_is_this_timestep:
                    # ...then head to the ballpark...
                    for stakeholder in {self.manager, self.scout
                                        } | self.players:
                        stakeholder.person.go_to(
                            destination=ballpark_of_next_game,
                            occasion="baseball")
                    # ...and let the league know the game is this timestep; League.operate() will
                    # eventually instantiate the actual Game object
                    self.league.add_to_game_queue(next_scheduled_series)
            # No game scheduled for this timestep, so just hang out
            except AttributeError:
                pass
        else:  # Off-season
            # Players will have already retired by LeagueSeason.review() calling
            # TeamSeason.review(); if anyone did retire, this team needs to sign
            # more players to get to the roster limit
            self._sign_players()