Permalink
Browse files

API extension for fpinging instances

It may be interesting for a cloud user or administrator to perform a
simple instance monitoring. A ping could be an acceptable solution: it
is fast and quite reliable. A limit for ping is 1 time a minute by
default.

API calls.

GET /os-fping?[all_tenants=1]&[include=uuid[,uuid...][&exclude=...]
Performs fping for all VM in the current project and returns results. If
`all_tenants` is requested, data for all projects is returned.
By default, `all_tenants` is allowed only for admins.
`include` and `exclude` are parameters specifying VM masks. Consider
that VM list is `VM_all`, then if `include` is set, the result will be
`VM_all * VM_include`. if `include` is set, the result will be `VM_all -
VM_exclude`. `exclude` is ignored if `include` is specified.

GET /os-fping/<vm-uuid>
Performs a check for single instance.

Configuration flags.

fping_path - full path to fping

Implement blueprint fping-instances-ext

Change-Id: I7d942270aa52bd6216eda0d7ae366ef0195d52a8
  • Loading branch information...
1 parent 6375ca7 commit a220aa15b056914df1b9debc95322d01a0e408e8 @aababilov aababilov committed Aug 29, 2012
View
@@ -46,6 +46,8 @@
"compute_extension:floating_ip_dns": "",
"compute_extension:floating_ip_pools": "",
"compute_extension:floating_ips": "",
+ "compute_extension:fping": "",
+ "compute_extension:fping:all_tenants": "rule:admin_api",
"compute_extension:hosts": "rule:admin_api",
"compute_extension:hypervisors": "rule:admin_api",
"compute_extension:instance_usage_audit_log": "rule:admin_api",
@@ -0,0 +1,162 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Grid Dynamics
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import itertools
+import os
+import time
+
+from webob import exc
+
+from nova.api.openstack import common
+from nova.api.openstack import extensions
+from nova import compute
+from nova import exception
+from nova import flags
+from nova.openstack.common import cfg
+from nova.openstack.common import log as logging
+from nova import utils
+
+
+LOG = logging.getLogger(__name__)
+authorize = extensions.extension_authorizer('compute', 'fping')
+authorize_all_tenants = extensions.extension_authorizer(
+ 'compute', 'fping:all_tenants')
+fping_opts = [
+ cfg.StrOpt("fping_path",
+ default="/usr/sbin/fping",
+ help="Full path to fping."),
+]
+
+FLAGS = flags.FLAGS
+FLAGS.register_opts(fping_opts)
+
+
+class FpingController(object):
+
+ def __init__(self, network_api=None):
+ self.compute_api = compute.API()
+ self.last_call = {}
+
+ def check_fping(self):
+ if not os.access(FLAGS.fping_path, os.X_OK):
+ raise exc.HTTPServiceUnavailable(
+ explanation=_("fping utility is not found."))
+
+ @staticmethod
+ def fping(ips):
+ fping_ret = utils.execute(FLAGS.fping_path, *ips,
+ check_exit_code=False)
+ if not fping_ret:
+ return set()
+ alive_ips = set()
+ for line in fping_ret[0].split("\n"):
+ ip = line.split(" ", 1)[0]
+ if "alive" in line:
+ alive_ips.add(ip)
+ return alive_ips
+
+ @staticmethod
+ def _get_instance_ips(context, instance):
+ ret = []
+ for network in common.get_networks_for_instance(
+ context, instance).values():
+ all_ips = itertools.chain(network["ips"], network["floating_ips"])
+ ret += [ip["address"] for ip in all_ips]
+ return ret
+
+ def index(self, req):
+ context = req.environ["nova.context"]
+ search_opts = dict(deleted=False)
+ if "all_tenants" in req.GET:
+ authorize_all_tenants(context)
+ else:
+ authorize(context)
+ if context.project_id:
+ search_opts["project_id"] = context.project_id
+ else:
+ search_opts["user_id"] = context.user_id
+ self.check_fping()
+ include = req.GET.get("include", None)
+ if include:
+ include = set(include.split(","))
+ exclude = set()
+ else:
+ include = None
+ exclude = req.GET.get("exclude", None)
+ if exclude:
+ exclude = set(exclude.split(","))
+ else:
+ exclude = set()
+
+ instance_list = self.compute_api.get_all(
+ context, search_opts=search_opts)
+ ip_list = []
+ instance_ips = {}
+ instance_projects = {}
+
+ for instance in instance_list:
+ uuid = instance["uuid"]
+ if uuid in exclude or (include is not None and
+ uuid not in include):
+ continue
+ ips = [str(ip) for ip in self._get_instance_ips(context, instance)]
+ instance_ips[uuid] = ips
+ instance_projects[uuid] = instance["project_id"]
+ ip_list += ips
+ alive_ips = self.fping(ip_list)
+ res = []
+ for instance_uuid, ips in instance_ips.iteritems():
+ res.append({
+ "id": instance_uuid,
+ "project_id": instance_projects[instance_uuid],
+ "alive": bool(set(ips) & alive_ips),
+ })
+ return {"servers": res}
+
+ def show(self, req, id):
+ try:
+ context = req.environ["nova.context"]
+ authorize(context)
+ self.check_fping()
+ instance = self.compute_api.get(context, id)
+ ips = [str(ip) for ip in self._get_instance_ips(context, instance)]
+ alive_ips = self.fping(ips)
+ return {
+ "server": {
+ "id": instance["uuid"],
+ "project_id": instance["project_id"],
+ "alive": bool(set(ips) & alive_ips),
+ }
+ }
+ except exception.NotFound:
+ raise exc.HTTPNotFound()
+
+
+class Fping(extensions.ExtensionDescriptor):
+ """Fping Management Extension."""
+
+ name = "Fping"
+ alias = "os-fping"
+ namespace = "http://docs.openstack.org/compute/ext/fping/api/v1.1"
+ updated = "2012-07-06T00:00:00+00:00"
+
+ def get_resources(self):
+ res = extensions.ResourceExtension(
+ "os-fping",
+ FpingController())
+ return [res]
@@ -212,6 +212,7 @@ def display(self):
Limit("PUT", "*", ".*", 10, PER_MINUTE),
Limit("GET", "*changes-since*", ".*changes-since.*", 3, PER_MINUTE),
Limit("DELETE", "*", ".*", 100, PER_MINUTE),
+ Limit("GET", "*/os-fping", "^/os-fping", 12, PER_HOUR),
]
@@ -0,0 +1,94 @@
+# Copyright 2011 Grid Dynamics
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from nova.api.openstack.compute.contrib import fping
+from nova.api.openstack import extensions
+from nova import exception
+from nova import test
+from nova.tests.api.openstack import fakes
+import nova.utils
+
+
+FAKE_UUID = fakes.FAKE_UUID
+
+
+def execute(*cmd, **args):
+ return "".join(["%s is alive" % ip for ip in cmd[1:]])
+
+
+class FpingTest(test.TestCase):
+
+ def setUp(self):
+ super(FpingTest, self).setUp()
+ self.flags(verbose=True, use_ipv6=False)
+ return_server = fakes.fake_instance_get()
+ return_servers = fakes.fake_instance_get_all_by_filters()
+ self.stubs.Set(nova.db, "instance_get_all_by_filters",
+ return_servers)
+ self.stubs.Set(nova.db, "instance_get_by_uuid",
+ return_server)
+ self.stubs.Set(nova.db, "instance_get_all_by_project",
+ return_servers)
+ self.stubs.Set(nova.utils, "execute",
+ execute)
+ self.stubs.Set(fping.FpingController, "check_fping",
+ lambda self: None)
+ self.ext_mgr = extensions.ExtensionManager()
+ self.ext_mgr.extensions = {}
+ self.controller = fping.FpingController(self.ext_mgr)
+
+ def test_fping_index(self):
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping")
+ res_dict = self.controller.index(req)
+ self.assertTrue("servers" in res_dict)
+ for srv in res_dict["servers"]:
+ for key in "project_id", "id", "alive":
+ self.assertTrue(key in srv)
+
+ def test_fping_index_policy(self):
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping?all_tenants=1")
+ self.assertRaises(exception.NotAuthorized, self.controller.index, req)
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping?all_tenants=1")
+ req.environ["nova.context"].is_admin = True
+ res_dict = self.controller.index(req)
+ self.assertTrue("servers" in res_dict)
+
+ def test_fping_index_include(self):
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping")
+ res_dict = self.controller.index(req)
+ ids = [srv["id"] for srv in res_dict["servers"]]
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping?include=%s" % ids[0])
+ res_dict = self.controller.index(req)
+ self.assertEqual(len(res_dict["servers"]), 1)
+ self.assertEqual(res_dict["servers"][0]["id"], ids[0])
+
+ def test_fping_index_exclude(self):
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping")
+ res_dict = self.controller.index(req)
+ ids = [srv["id"] for srv in res_dict["servers"]]
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping?exclude=%s" %
+ ",".join(ids[1:]))
+ res_dict = self.controller.index(req)
+ self.assertEqual(len(res_dict["servers"]), 1)
+ self.assertEqual(res_dict["servers"][0]["id"], ids[0])
+
+ def test_fping_show(self):
+ req = fakes.HTTPRequest.blank("/v2/1234/os-fping/%s" % FAKE_UUID)
+ res_dict = self.controller.show(req, FAKE_UUID)
+ self.assertTrue("server" in res_dict)
+ srv = res_dict["server"]
+ for key in "project_id", "id", "alive":
+ self.assertTrue(key in srv)
@@ -217,6 +217,14 @@
"updated": "%(timestamp)s"
},
{
+ "alias": "os-fping",
+ "description": "%(text)s",
+ "links": [],
+ "name": "Fping",
+ "namespace": "http://docs.openstack.org/compute/ext/fping/api/v1.1",
+ "updated": "%(timestamp)s"
+ },
+ {
"alias": "os-hypervisors",
"description": "%(text)s",
"links": [],
@@ -81,6 +81,9 @@
<extension alias="os-services" name="Services" namespace="http://docs.openstack.org/compute/ext/services/api/v2" updated="%(timestamp)s">
<description>%(text)s</description>
</extension>
+ <extension alias="os-fping" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/fping/api/v1.1" name="Fping">
+ <description>%(text)s</description>
+ </extension>
<extension alias="os-hypervisors" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/hypervisors/api/v1.1" name="Hypervisors">
<description>%(text)s</description>
</extension>
@@ -66,6 +66,19 @@
],
"regex": ".*changes-since.*",
"uri": "*changes-since*"
+ },
+ {
+ "limit": [
+ {
+ "next-available": "%(timestamp)s",
+ "remaining": 12,
+ "unit": "HOUR",
+ "value": 12,
+ "verb": "GET"
+ }
+ ],
+ "regex": "^/os-fping",
+ "uri": "*/os-fping"
}
]
}
@@ -12,6 +12,9 @@
<rate regex=".*changes-since.*" uri="*changes-since*">
<limit next-available="%(timestamp)s" unit="MINUTE" verb="GET" remaining="3" value="3"/>
</rate>
+ <rate regex="^/os-fping" uri="*/os-fping">
+ <limit next-available="%(timestamp)s" unit="HOUR" verb="GET" remaining="12" value="12"/>
+ </rate>
</rates>
<absolute>
<limit name="maxServerMeta" value="128"/>
@@ -71,6 +71,19 @@
],
"regex": ".*changes-since.*",
"uri": "*changes-since*"
+ },
+ {
+ "limit": [
+ {
+ "next-available": "%(timestamp)s",
+ "remaining": 12,
+ "unit": "HOUR",
+ "value": 12,
+ "verb": "GET"
+ }
+ ],
+ "regex": "^/os-fping",
+ "uri": "*/os-fping"
}
]
}
@@ -12,6 +12,9 @@
<rate regex=".*changes-since.*" uri="*changes-since*">
<limit next-available="%(timestamp)s" unit="MINUTE" verb="GET" remaining="3" value="3"/>
</rate>
+ <rate regex="^/os-fping" uri="*/os-fping">
+ <limit next-available="%(timestamp)s" unit="HOUR" verb="GET" remaining="12" value="12"/>
+ </rate>
</rates>
<absolute>
<limit name="maxServerMeta" value="128"/>
View
@@ -104,6 +104,8 @@
"compute_extension:floating_ip_dns": "",
"compute_extension:floating_ip_pools": "",
"compute_extension:floating_ips": "",
+ "compute_extension:fping": "",
+ "compute_extension:fping:all_tenants": "is_admin:True",
"compute_extension:hosts": "",
"compute_extension:hypervisors": "",
"compute_extension:instance_usage_audit_log": "",

0 comments on commit a220aa1

Please sign in to comment.