diff options
author | Alessio Ababilov <aababilov@griddynamics.com> | 2012-08-29 19:16:37 +0300 |
---|---|---|
committer | Alessio Ababilov <aababilov@griddynamics.com> | 2012-11-14 11:16:47 +0200 |
commit | a220aa15b056914df1b9debc95322d01a0e408e8 (patch) | |
tree | 067c5b9c1898a44dc91aefab0445f90ad4049add | |
parent | 6375ca7eb56b13bb4794b843a5eaef8118f0dff6 (diff) | |
download | nova-a220aa15b056914df1b9debc95322d01a0e408e8.tar.gz nova-a220aa15b056914df1b9debc95322d01a0e408e8.tar.xz nova-a220aa15b056914df1b9debc95322d01a0e408e8.zip |
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
11 files changed, 304 insertions, 0 deletions
diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 50d55d58d..27f45313e 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -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", diff --git a/nova/api/openstack/compute/contrib/fping.py b/nova/api/openstack/compute/contrib/fping.py new file mode 100644 index 000000000..3ae876f5a --- /dev/null +++ b/nova/api/openstack/compute/contrib/fping.py @@ -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] diff --git a/nova/api/openstack/compute/limits.py b/nova/api/openstack/compute/limits.py index c0ef65670..767280a45 100644 --- a/nova/api/openstack/compute/limits.py +++ b/nova/api/openstack/compute/limits.py @@ -212,6 +212,7 @@ DEFAULT_LIMITS = [ 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), ] diff --git a/nova/tests/api/openstack/compute/contrib/test_fping.py b/nova/tests/api/openstack/compute/contrib/test_fping.py new file mode 100644 index 000000000..9a838162a --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_fping.py @@ -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) diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index 34f305967..ed2c27492 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -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": [], diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index 44aa5ee81..e3d454966 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -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> diff --git a/nova/tests/integrated/api_samples/limit-get-resp.json.tpl b/nova/tests/integrated/api_samples/limit-get-resp.json.tpl index 7763dd683..a86d5faa2 100644 --- a/nova/tests/integrated/api_samples/limit-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/limit-get-resp.json.tpl @@ -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" } ] } diff --git a/nova/tests/integrated/api_samples/limit-get-resp.xml.tpl b/nova/tests/integrated/api_samples/limit-get-resp.xml.tpl index 860499f8d..6f92bcee6 100644 --- a/nova/tests/integrated/api_samples/limit-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/limit-get-resp.xml.tpl @@ -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"/> diff --git a/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.json.tpl index 3eef6c68b..78ddbb5af 100644 --- a/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.json.tpl @@ -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" } ] } diff --git a/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.xml.tpl index b8e0396e8..75526473a 100644 --- a/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-used-limits/usedlimits-get-resp.xml.tpl @@ -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"/> diff --git a/nova/tests/policy.json b/nova/tests/policy.json index bf94d4e49..85bf5b583 100644 --- a/nova/tests/policy.json +++ b/nova/tests/policy.json @@ -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": "", |