We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
kubernetes probes using HealthHandler endpoint succeed when models are not ready
/kind bug

**What steps did you take and what happened:**
Used the [HealthHandler endpoint](https://github.com/kubeflow/kfserving/blob/92c5f3c9df662c853beef86bddf7f30ab5d8b996/python/kfserving/kfserving/kfserver.py#L62-L63) to set up a Kubernetes readiness probe. The probe succeeded even though the model was not ready. 

**What did you expect to happen:**
Readiness probe failure when model is not ready. 

**Anything else you would like to add:**
[Kubernetes probe documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) says `Any code greater than or equal to 200 and less than 400 indicates success. Any other code indicates failure`, which seems to imply that the [response contents](https://github.com/kubeflow/kfserving/blob/92c5f3c9df662c853beef86bddf7f30ab5d8b996/python/kfserving/kfserving/kfserver.py#L108-L111) (which include the model's `.ready` property) are ignored. 


**Environment:**

- Istio Version:
- Knative Version:
- KFServing Version:
- Kubeflow version:
- Minikube version:
- Kubernetes version: (use `kubectl version`):
- OS (e.g. from `/etc/os-release`):


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `python/kfserving/kfserving/kfserver.py`
Content:
```
1 # Copyright 2020 kubeflow.org.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #    http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import argparse
16 import logging
17 import json
18 from typing import List, Dict
19 import tornado.ioloop
20 import tornado.web
21 import tornado.httpserver
22 import tornado.log
23 from kfserving.handlers.http import PredictHandler, ExplainHandler
24 from kfserving import KFModel
25 
26 DEFAULT_HTTP_PORT = 8080
27 DEFAULT_GRPC_PORT = 8081
28 DEFAULT_MAX_BUFFER_SIZE = 104857600
29 
30 parser = argparse.ArgumentParser(add_help=False)
31 parser.add_argument('--http_port', default=DEFAULT_HTTP_PORT, type=int,
32                     help='The HTTP Port listened to by the model server.')
33 parser.add_argument('--grpc_port', default=DEFAULT_GRPC_PORT, type=int,
34                     help='The GRPC Port listened to by the model server.')
35 parser.add_argument('--max_buffer_size', default=DEFAULT_MAX_BUFFER_SIZE, type=int,
36                     help='The max buffer size for tornado.')
37 parser.add_argument('--workers', default=0, type=int,
38                     help='The number of works to fork')
39 args, _ = parser.parse_known_args()
40 
41 tornado.log.enable_pretty_logging()
42 
43 class KFServer:
44     def __init__(self, http_port: int = args.http_port,
45                  grpc_port: int = args.grpc_port,
46                  max_buffer_size: int = args.max_buffer_size,
47                  workers: int = args.workers):
48         self.registered_models = {}
49         self.http_port = http_port
50         self.grpc_port = grpc_port
51         self.max_buffer_size = max_buffer_size
52         self.workers = workers
53         self._http_server = None
54 
55     def create_application(self):
56         return tornado.web.Application([
57             # Server Liveness API returns 200 if server is alive.
58             (r"/", LivenessHandler),
59             (r"/v1/models",
60              ListHandler, dict(models=self.registered_models)),
61             # Model Health API returns 200 if model is ready to serve.
62             (r"/v1/models/([a-zA-Z0-9_-]+)",
63              HealthHandler, dict(models=self.registered_models)),
64             (r"/v1/models/([a-zA-Z0-9_-]+):predict",
65              PredictHandler, dict(models=self.registered_models)),
66             (r"/v1/models/([a-zA-Z0-9_-]+):explain",
67              ExplainHandler, dict(models=self.registered_models)),
68         ])
69 
70     def start(self, models: List[KFModel]):
71         for model in models:
72             self.register_model(model)
73 
74         self._http_server = tornado.httpserver.HTTPServer(
75             self.create_application(), max_buffer_size=self.max_buffer_size)
76 
77         logging.info("Listening on port %s", self.http_port)
78         self._http_server.bind(self.http_port)
79         logging.info("Will fork %d workers", self.workers)
80         self._http_server.start(self.workers)
81         tornado.ioloop.IOLoop.current().start()
82 
83     def register_model(self, model: KFModel):
84         if not model.name:
85             raise Exception(
86                 "Failed to register model, model.name must be provided.")
87         self.registered_models[model.name] = model
88         logging.info("Registering model: %s", model.name)
89 
90 
91 class LivenessHandler(tornado.web.RequestHandler):  # pylint:disable=too-few-public-methods
92     def get(self):
93         self.write("Alive")
94 
95 
96 class HealthHandler(tornado.web.RequestHandler):
97     def initialize(self, models: Dict[str, KFModel]):
98         self.models = models  # pylint:disable=attribute-defined-outside-init
99 
100     def get(self, name: str):
101         if name not in self.models:
102             raise tornado.web.HTTPError(
103                 status_code=404,
104                 reason="Model with name %s does not exist." % name
105             )
106 
107         model = self.models[name]
108         self.write(json.dumps({
109             "name": model.name,
110             "ready": model.ready
111         }))
112 
113 
114 class ListHandler(tornado.web.RequestHandler):
115     def initialize(self, models: Dict[str, KFModel]):
116         self.models = models  # pylint:disable=attribute-defined-outside-init
117 
118     def get(self):
119         self.write(json.dumps(list(self.models.values())))
120
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/python/kfserving/kfserving/kfserver.py b/python/kfserving/kfserving/kfserver.py
--- a/python/kfserving/kfserving/kfserver.py
+++ b/python/kfserving/kfserving/kfserver.py
@@ -105,6 +105,12 @@
             )
 
         model = self.models[name]
+        if not model.ready:
+            raise tornado.web.HTTPError(
+                status_code=503,
+                reason="Model with name %s is not ready." % name
+            )
+
         self.write(json.dumps({
             "name": model.name,
             "ready": model.ready

{"golden_diff": "diff --git a/python/kfserving/kfserving/kfserver.py b/python/kfserving/kfserving/kfserver.py\n--- a/python/kfserving/kfserving/kfserver.py\n+++ b/python/kfserving/kfserving/kfserver.py\n@@ -105,6 +105,12 @@\n             )\n \n         model = self.models[name]\n+        if not model.ready:\n+            raise tornado.web.HTTPError(\n+                status_code=503,\n+                reason=\"Model with name %s is not ready.\" % name\n+            )\n+\n         self.write(json.dumps({\n             \"name\": model.name,\n             \"ready\": model.ready\n", "issue": "kubernetes probes using HealthHandler endpoint succeed when models are not ready\n/kind bug\r\n\r\n**What steps did you take and what happened:**\r\nUsed the [HealthHandler endpoint](https://github.com/kubeflow/kfserving/blob/92c5f3c9df662c853beef86bddf7f30ab5d8b996/python/kfserving/kfserving/kfserver.py#L62-L63) to set up a Kubernetes readiness probe. The probe succeeded even though the model was not ready. \r\n\r\n**What did you expect to happen:**\r\nReadiness probe failure when model is not ready. \r\n\r\n**Anything else you would like to add:**\r\n[Kubernetes probe documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) says `Any code greater than or equal to 200 and less than 400 indicates success. Any other code indicates failure`, which seems to imply that the [response contents](https://github.com/kubeflow/kfserving/blob/92c5f3c9df662c853beef86bddf7f30ab5d8b996/python/kfserving/kfserving/kfserver.py#L108-L111) (which include the model's `.ready` property) are ignored. \r\n\r\n\r\n**Environment:**\r\n\r\n- Istio Version:\r\n- Knative Version:\r\n- KFServing Version:\r\n- Kubeflow version:\r\n- Minikube version:\r\n- Kubernetes version: (use `kubectl version`):\r\n- OS (e.g. from `/etc/os-release`):\r\n\n", "before_files": [{"content": "# Copyright 2020 kubeflow.org.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport argparse\nimport logging\nimport json\nfrom typing import List, Dict\nimport tornado.ioloop\nimport tornado.web\nimport tornado.httpserver\nimport tornado.log\nfrom kfserving.handlers.http import PredictHandler, ExplainHandler\nfrom kfserving import KFModel\n\nDEFAULT_HTTP_PORT = 8080\nDEFAULT_GRPC_PORT = 8081\nDEFAULT_MAX_BUFFER_SIZE = 104857600\n\nparser = argparse.ArgumentParser(add_help=False)\nparser.add_argument('--http_port', default=DEFAULT_HTTP_PORT, type=int,\n                    help='The HTTP Port listened to by the model server.')\nparser.add_argument('--grpc_port', default=DEFAULT_GRPC_PORT, type=int,\n                    help='The GRPC Port listened to by the model server.')\nparser.add_argument('--max_buffer_size', default=DEFAULT_MAX_BUFFER_SIZE, type=int,\n                    help='The max buffer size for tornado.')\nparser.add_argument('--workers', default=0, type=int,\n                    help='The number of works to fork')\nargs, _ = parser.parse_known_args()\n\ntornado.log.enable_pretty_logging()\n\nclass KFServer:\n    def __init__(self, http_port: int = args.http_port,\n                 grpc_port: int = args.grpc_port,\n                 max_buffer_size: int = args.max_buffer_size,\n                 workers: int = args.workers):\n        self.registered_models = {}\n        self.http_port = http_port\n        self.grpc_port = grpc_port\n        self.max_buffer_size = max_buffer_size\n        self.workers = workers\n        self._http_server = None\n\n    def create_application(self):\n        return tornado.web.Application([\n            # Server Liveness API returns 200 if server is alive.\n            (r\"/\", LivenessHandler),\n            (r\"/v1/models\",\n             ListHandler, dict(models=self.registered_models)),\n            # Model Health API returns 200 if model is ready to serve.\n            (r\"/v1/models/([a-zA-Z0-9_-]+)\",\n             HealthHandler, dict(models=self.registered_models)),\n            (r\"/v1/models/([a-zA-Z0-9_-]+):predict\",\n             PredictHandler, dict(models=self.registered_models)),\n            (r\"/v1/models/([a-zA-Z0-9_-]+):explain\",\n             ExplainHandler, dict(models=self.registered_models)),\n        ])\n\n    def start(self, models: List[KFModel]):\n        for model in models:\n            self.register_model(model)\n\n        self._http_server = tornado.httpserver.HTTPServer(\n            self.create_application(), max_buffer_size=self.max_buffer_size)\n\n        logging.info(\"Listening on port %s\", self.http_port)\n        self._http_server.bind(self.http_port)\n        logging.info(\"Will fork %d workers\", self.workers)\n        self._http_server.start(self.workers)\n        tornado.ioloop.IOLoop.current().start()\n\n    def register_model(self, model: KFModel):\n        if not model.name:\n            raise Exception(\n                \"Failed to register model, model.name must be provided.\")\n        self.registered_models[model.name] = model\n        logging.info(\"Registering model: %s\", model.name)\n\n\nclass LivenessHandler(tornado.web.RequestHandler):  # pylint:disable=too-few-public-methods\n    def get(self):\n        self.write(\"Alive\")\n\n\nclass HealthHandler(tornado.web.RequestHandler):\n    def initialize(self, models: Dict[str, KFModel]):\n        self.models = models  # pylint:disable=attribute-defined-outside-init\n\n    def get(self, name: str):\n        if name not in self.models:\n            raise tornado.web.HTTPError(\n                status_code=404,\n                reason=\"Model with name %s does not exist.\" % name\n            )\n\n        model = self.models[name]\n        self.write(json.dumps({\n            \"name\": model.name,\n            \"ready\": model.ready\n        }))\n\n\nclass ListHandler(tornado.web.RequestHandler):\n    def initialize(self, models: Dict[str, KFModel]):\n        self.models = models  # pylint:disable=attribute-defined-outside-init\n\n    def get(self):\n        self.write(json.dumps(list(self.models.values())))\n", "path": "python/kfserving/kfserving/kfserver.py"}], "after_files": [{"content": "# Copyright 2020 kubeflow.org.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport argparse\nimport logging\nimport json\nfrom typing import List, Dict\nimport tornado.ioloop\nimport tornado.web\nimport tornado.httpserver\nimport tornado.log\nfrom kfserving.handlers.http import PredictHandler, ExplainHandler\nfrom kfserving import KFModel\n\nDEFAULT_HTTP_PORT = 8080\nDEFAULT_GRPC_PORT = 8081\nDEFAULT_MAX_BUFFER_SIZE = 104857600\n\nparser = argparse.ArgumentParser(add_help=False)\nparser.add_argument('--http_port', default=DEFAULT_HTTP_PORT, type=int,\n                    help='The HTTP Port listened to by the model server.')\nparser.add_argument('--grpc_port', default=DEFAULT_GRPC_PORT, type=int,\n                    help='The GRPC Port listened to by the model server.')\nparser.add_argument('--max_buffer_size', default=DEFAULT_MAX_BUFFER_SIZE, type=int,\n                    help='The max buffer size for tornado.')\nparser.add_argument('--workers', default=0, type=int,\n                    help='The number of works to fork')\nargs, _ = parser.parse_known_args()\n\ntornado.log.enable_pretty_logging()\n\nclass KFServer:\n    def __init__(self, http_port: int = args.http_port,\n                 grpc_port: int = args.grpc_port,\n                 max_buffer_size: int = args.max_buffer_size,\n                 workers: int = args.workers):\n        self.registered_models = {}\n        self.http_port = http_port\n        self.grpc_port = grpc_port\n        self.max_buffer_size = max_buffer_size\n        self.workers = workers\n        self._http_server = None\n\n    def create_application(self):\n        return tornado.web.Application([\n            # Server Liveness API returns 200 if server is alive.\n            (r\"/\", LivenessHandler),\n            (r\"/v1/models\",\n             ListHandler, dict(models=self.registered_models)),\n            # Model Health API returns 200 if model is ready to serve.\n            (r\"/v1/models/([a-zA-Z0-9_-]+)\",\n             HealthHandler, dict(models=self.registered_models)),\n            (r\"/v1/models/([a-zA-Z0-9_-]+):predict\",\n             PredictHandler, dict(models=self.registered_models)),\n            (r\"/v1/models/([a-zA-Z0-9_-]+):explain\",\n             ExplainHandler, dict(models=self.registered_models)),\n        ])\n\n    def start(self, models: List[KFModel]):\n        for model in models:\n            self.register_model(model)\n\n        self._http_server = tornado.httpserver.HTTPServer(\n            self.create_application(), max_buffer_size=self.max_buffer_size)\n\n        logging.info(\"Listening on port %s\", self.http_port)\n        self._http_server.bind(self.http_port)\n        logging.info(\"Will fork %d workers\", self.workers)\n        self._http_server.start(self.workers)\n        tornado.ioloop.IOLoop.current().start()\n\n    def register_model(self, model: KFModel):\n        if not model.name:\n            raise Exception(\n                \"Failed to register model, model.name must be provided.\")\n        self.registered_models[model.name] = model\n        logging.info(\"Registering model: %s\", model.name)\n\n\nclass LivenessHandler(tornado.web.RequestHandler):  # pylint:disable=too-few-public-methods\n    def get(self):\n        self.write(\"Alive\")\n\n\nclass HealthHandler(tornado.web.RequestHandler):\n    def initialize(self, models: Dict[str, KFModel]):\n        self.models = models  # pylint:disable=attribute-defined-outside-init\n\n    def get(self, name: str):\n        if name not in self.models:\n            raise tornado.web.HTTPError(\n                status_code=404,\n                reason=\"Model with name %s does not exist.\" % name\n            )\n\n        model = self.models[name]\n        if not model.ready:\n            raise tornado.web.HTTPError(\n                status_code=503,\n                reason=\"Model with name %s is not ready.\" % name\n            )\n\n        self.write(json.dumps({\n            \"name\": model.name,\n            \"ready\": model.ready\n        }))\n\n\nclass ListHandler(tornado.web.RequestHandler):\n    def initialize(self, models: Dict[str, KFModel]):\n        self.models = models  # pylint:disable=attribute-defined-outside-init\n\n    def get(self):\n        self.write(json.dumps(list(self.models.values())))\n", "path": "python/kfserving/kfserving/kfserver.py"}]}