Пример #1
0
class Game:
    def __init__(self, executor, vmManager, scoreboard, scanner, gameName,
                 scenarioName):
        self.gameName = gameName

        # executor is a concurrent.futures.Executor class that allows us to
        # run stuff.
        self.executor = executor

        # Store the scoreboard and scanner for future use.
        self.scoreboard = scoreboard
        self.scanner = scanner

        # Load the scenario
        self.scenarioName = scenarioName
        self.scenario = Game.LoadScenario(scenarioName)

        # The state that the game is in, needs to happen before starting gameTask.
        self.state = GameState.GAME_CREATED

        # Start the Game Task, which runs anything in the game that need to
        # be done repeatedly, such as scoring the user.
        self.gameTaskExit = False
        self.gameTask = executor.submit(Game._GameFunc, self)

        # Create the guest agent proxy.
        self.agent = GuestAgent(self.executor, self.GetIP())

        # Init the player variables.
        self.users = {}
        self.portToUser = {}
        self.pidToUser = {}

        # Create the VM that we'll be using for this game.
        self.vmManager = vmManager
        self.vm = vmManager.CreateVM(self.scenario['vmPath'],
                                     self.scenarioName)

        # Create the scorer for scoring the users.
        self.scorer = Scorer(self)

    def __del__(self):
        self.StopGameFunc()

    # Start the game.
    def Start(self):
        # Set it to we are starting.
        self.state = GameState.GAME_STARTING1
        # GameFunc will handle the rest.
        # ie. Initialize the VM, waiting for guest agent... etc
        return KOFErrorCode.ERROR_NONE

    # Destroy the game.
    def Destroy(self):
        if self.state != GameState.GAME_RUNNING:
            logging.warn("Destroying game not in running state: %s %s" %
                         (self.gameName, str(self.state)))
        self.state = GameState.GAME_DESTROYING
        return KOFErrorCode.ERROR_NONE

    def PlayerIssueCmd(self, playerName, cmd):
        if self.state != GameState.GAME_RUNNING:
            logging.info(
                "Player %s issued command when game %s is not running." %
                (playerName, self.gameName))
            return KOFErrorCode.ERROR_GAME_NOT_RUNNING
        if not self.scenario['allowCommand']:
            logging.info(
                "Player %s tried to issue command when game %s doesn't allow."
                % (playerName, self.gameName))
            return KOFErrorCode.ERROR_GAME_NOT_ALLOW
        if playerName not in self.users:
            logging.info("Player %s not register id game %s." %
                         (playerName, self.gameName))
            return KOFErrorCode.ERROR_USER_NOT_REGISTERED
        res = self.agent.RunCmd(cmd)
        if res.reply.error != GuestErrorCode.ERROR_NONE:
            logging.warning(
                "Executing command '%s' failed due to agent problem %s." %
                (cmd, res.reply.error))
            return KOFErrorCode.ERROR_AGENT_PROBLEM
        self.SetPlayerPID(playerName, res.pid)
        return KOFErrorCode.ERROR_NONE

    def SetPlayerPID(self, playerName, pid):
        assert playerName in self.users
        self.users[playerName]['pid'] = pid
        self.pidToUser[pid] = playerName

    # Return the Game protobuf for this game.
    def GetGameProto(self):
        result = kofserver_pb2.Game(name=self.gameName, state=self.state)
        return result

    def _GameFunc(self):
        # We need to catch the exception because it doesn't showup until
        # very late, when .result() is called.
        try:
            self._GameFuncReal()
        except Exception:
            logging.exception("Exception in GameFunc")

    def _GameFuncReal(self):
        logging.info("GameFunc for %s running" % (self.gameName, ))
        # This method runs for the entire time life cycle of the game.
        while True:
            if self.gameTaskExit:
                # Time to go
                return True

            if self.state == GameState.GAME_CREATED:
                # Nothing to do
                time.sleep(0.5)
                continue

            if self.state == GameState.GAME_STARTING1:
                if self.vm.GetState() == VM.VMState.CREATED:
                    # Init and start the VM
                    res = self.vm.Init()
                    if not res or self.vm.GetState() != VM.VMState.READY:
                        logging.error(
                            "Failed to init VM (%s) or invalid VM state after Init() (%s)"
                            % (str(res), str(self.vm.GetState())))
                        self.state = GameState.GAME_ERROR
                        continue

                    res = self.vm.Boot()
                    if not res:
                        logging.error("Failed to boot VM")
                        self.state = GameState.GAME_ERROR
                        continue

                    continue
                else:
                    # We've finished init and start VM step, so wait for it
                    # to be running.
                    if self.vm.GetState() == VM.VMState.RUNNING:
                        logging.info("VM for Game %s is running." %
                                     (self.gameName, ))
                        self.state = GameState.GAME_STARTING2
                    # Wait some time?
                    time.sleep(0.5)

            if self.state == GameState.GAME_STARTING2:
                # Wait for the guest agent to be connected.
                if self.agent.EnsureConnection():
                    # It's connected, so we can move onto the started state
                    logging.info("Agent for Game %s is ready." %
                                 (self.gameName, ))
                    self.state = GameState.GAME_RUNNING
                    self.scorer.NotifyGameStarted()
                    continue
                # If agent is responsive, then we don't have to check the VM.
                # Wait and try again.
                time.sleep(0.5)
                continue

            if self.state == GameState.GAME_RUNNING:
                playerScored = self.scorer.TryScorePlayers()
                if not playerScored:
                    # Don't stress it too much.
                    time.sleep(0.3)

            if self.state == GameState.GAME_REBOOTING:
                raise Exception("Reboot state not implemented yet")
                # TODO

            if self.state == GameState.GAME_DESTROYING:
                # Shutdown the VM and reset the guest agent.
                self.agent.ResetConnection()
                if self.vm.GetState() == VM.VMState.RUNNING:
                    result = self.vm.Shutdown()
                    if not result:
                        logging.error("Failed to shutdown VM")
                        self.state = GameState.GAME_ERROR
                        continue

                if self.vm.GetState() == VM.VMState.READY:
                    # Let's destroy it.
                    result = self.vm.Destroy()
                    if not result:
                        logging.error("Failed to destroy VM")
                        self.state = GameState.GAME_ERROR
                        continue
                    self.state = GameState.GAME_DESTROYED

                # Wait for the VM to get to shutdown state.
                if self.vm.GetState() == VM.VMState.DESTROYED:
                    # It's down, so let's
                    # TODO
                    pass

    def StopGameFunc(self):
        # Note: Should never be called from GameFunc, or it'll deadlock.
        self.gameTaskExit = True
        # Wait for it to end by asking for its result.
        self.gameTask.result()

    def GetIP(self):
        return self.scenario['ip']

    def RegisterPlayer(self, username):
        if username in self.users:
            return KOFErrorCode.ERROR_USER_ALREADY_EXISTS

        self.users[username] = {}

        # Generate a port for the user.
        for i in range(1000000):
            # Not a while True so if something went wrong we know.

            port = random.randint(Config.conf()['portStart'],
                                  Config.conf()['portEnd'])
            if port in self.portToUser:
                port = -1
                continue
            break
        if port == -1:
            # This shouldn't happen, so it's an Exception not a return code.
            raise Exception("No valid port available")
        self.portToUser[port] = username
        self.users[username]["port"] = port
        self.users[username]["pid"] = -1  # Not available yet.
        self.users[username][
            "pidUp"] = False  # User's pid up when we last check?
        self.users[username][
            "portUp"] = False  # User's port up when we last check?

        return KOFErrorCode.ERROR_NONE

    def QueryPlayerInfo(self, playerName):
        if playerName != "":
            return [
                self.GetPlayerInfoProto(playerName),
            ]
        # playerName == "", so we retrieve all the players.
        res = []
        for p in self.users:
            res.append(self.GetPlayerInfoProto(p))
        return res

    def GetPlayerInfoProto(self, playerName):
        if playerName not in self.users:
            raise Exception("Invalid player %s for GetPlayerInfoProto" %
                            playerName)

        result = kofserver_pb2.PlayerInfo(playerName=playerName)
        result.port = udict["port"]
        result.pid = udict["pid"]
        result.portUp = udict["portUp"]
        result.pidUp = udict["pidUp"]
        return result

    def Shutdown(self):
        self.state = GameState.GAME_ERROR
        if self.vm.GetState() == VM.VMState.RUNNING:
            result = self.vm.Shutdown()
            if not result:
                logging.error("Failed to shutdown VM during shutdown")

        if self.vm.GetState() == VM.VMState.READY:
            result = self.vm.Destroy()
            if not result:
                logging.error("Failed to destroy VM during shutdown")

        self.gameTaskExit = True
        # Wait for it to terminate
        self.gameTask.result()

    @staticmethod
    def LoadScenario(scenarioName):
        scenarioDir = Config.conf()['scenarioDir']
        scenarioDir = os.path.abspath(scenarioDir)
        scenarioPath = os.path.join(scenarioDir, scenarioName)
        ymlPath = os.path.join(scenarioPath, "scenario.yml")
        try:
            with open(ymlPath) as f:
                result = yaml.load(f, Loader=yaml.FullLoader)
        except Exception:
            logging.exception("Failed to open scenario.yml file %s" % ymlPath)
            raise
        # If vmPath is relative, we convert it to absolute here.
        if not os.path.isabs(result['vmPath']):
            result['vmPath'] = os.path.join(scenarioPath, result['vmPath'])
        return result