diff --git a/magnum/conductor/mesos_monitor.py b/magnum/conductor/mesos_monitor.py new file mode 100644 index 0000000000..d118a1f5e2 --- /dev/null +++ b/magnum/conductor/mesos_monitor.py @@ -0,0 +1,52 @@ +# 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 oslo_serialization import jsonutils + +from magnum.common import urlfetch +from magnum.conductor.monitors import MonitorBase + + +class MesosMonitor(MonitorBase): + + def __init__(self, context, bay): + super(MesosMonitor, self).__init__(context, bay) + self.data = {} + + @property + def metrics_spec(self): + return { + 'memory_util': { + 'unit': '%', + 'func': 'compute_memory_util', + }, + } + + def _build_url(self, url, protocol='http', port='80', path='/'): + return protocol + '://' + url + ':' + port + path + + def pull_data(self): + mesos_master_url = self._build_url(self.bay.api_address, + port='5050', + path='/state') + state_json = jsonutils.loads(urlfetch.get(mesos_master_url)) + self.data['mem_total'] = 0 + self.data['mem_used'] = 0 + for slave in state_json['slaves']: + self.data['mem_total'] += slave['resources']['mem'] + self.data['mem_used'] += slave['used_resources']['mem'] + + def compute_memory_util(self): + if self.data['mem_total'] == 0: + return 0 + else: + return self.data['mem_used'] * 100 / self.data['mem_total'] diff --git a/magnum/conductor/monitors.py b/magnum/conductor/monitors.py index cde8d4fe3a..45e63711c2 100644 --- a/magnum/conductor/monitors.py +++ b/magnum/conductor/monitors.py @@ -36,7 +36,8 @@ CONF.import_opt('default_timeout', COE_CLASS_PATH = { bay_type.SWARM: 'magnum.conductor.swarm_monitor.SwarmMonitor', - bay_type.KUBERNETES: 'magnum.conductor.k8s_monitor.K8sMonitor' + bay_type.KUBERNETES: 'magnum.conductor.k8s_monitor.K8sMonitor', + bay_type.MESOS: 'magnum.conductor.mesos_monitor.MesosMonitor' } @@ -73,6 +74,5 @@ def create_monitor(context, bay): coe_cls = importutils.import_class(COE_CLASS_PATH[baymodel.coe]) return coe_cls(context, bay) - # TODO(hongbin): add support for other bay types LOG.debug("Cannot create monitor with bay type '%s'" % baymodel.coe) return None diff --git a/magnum/tests/unit/conductor/test_monitors.py b/magnum/tests/unit/conductor/test_monitors.py index 2fee052b4a..1fa1025310 100644 --- a/magnum/tests/unit/conductor/test_monitors.py +++ b/magnum/tests/unit/conductor/test_monitors.py @@ -15,7 +15,10 @@ import mock +from oslo_serialization import jsonutils + from magnum.conductor import k8s_monitor +from magnum.conductor import mesos_monitor from magnum.conductor import monitors from magnum.conductor import swarm_monitor from magnum import objects @@ -44,6 +47,8 @@ class MonitorsTestCase(base.TestCase): self.bay = objects.Bay(self.context, **bay) self.monitor = swarm_monitor.SwarmMonitor(self.context, self.bay) self.k8s_monitor = k8s_monitor.K8sMonitor(self.context, self.bay) + self.mesos_monitor = mesos_monitor.MesosMonitor(self.context, + self.bay) p = mock.patch('magnum.conductor.swarm_monitor.SwarmMonitor.' 'metrics_spec', new_callable=mock.PropertyMock) self.mock_metrics_spec = p.start() @@ -66,6 +71,14 @@ class MonitorsTestCase(base.TestCase): monitor = monitors.create_monitor(self.context, self.bay) self.assertIsInstance(monitor, k8s_monitor.K8sMonitor) + @mock.patch('magnum.objects.BayModel.get_by_uuid') + def test_create_monitor_mesos_bay(self, mock_baymodel_get_by_uuid): + baymodel = mock.MagicMock() + baymodel.coe = 'mesos' + mock_baymodel_get_by_uuid.return_value = baymodel + monitor = monitors.create_monitor(self.context, self.bay) + self.assertIsInstance(monitor, mesos_monitor.MesosMonitor) + @mock.patch('magnum.objects.BayModel.get_by_uuid') def test_create_monitor_unsupported_coe(self, mock_baymodel_get_by_uuid): baymodel = mock.MagicMock() @@ -216,3 +229,58 @@ class MonitorsTestCase(base.TestCase): self.k8s_monitor.data = test_data mem_util = self.k8s_monitor.compute_memory_util() self.assertEqual(0, mem_util) + + @mock.patch('magnum.common.urlfetch.get') + def test_mesos_monitor_pull_data_success(self, mock_url_get): + state_json = { + 'slaves': [{ + 'resources': { + 'mem': 100 + }, + 'used_resources': { + 'mem': 50 + } + }] + } + state_json = jsonutils.dumps(state_json) + mock_url_get.return_value = state_json + self.mesos_monitor.pull_data() + self.assertEqual(self.mesos_monitor.data['mem_total'], + 100) + self.assertEqual(self.mesos_monitor.data['mem_used'], + 50) + + def test_mesos_monitor_get_metric_names(self): + mesos_metric_spec = 'magnum.conductor.mesos_monitor.MesosMonitor.'\ + 'metrics_spec' + with mock.patch(mesos_metric_spec, + new_callable=mock.PropertyMock) as mock_mesos_metric: + mock_mesos_metric.return_value = self.test_metrics_spec + names = self.mesos_monitor.get_metric_names() + self.assertEqual(sorted(['metric1', 'metric2']), sorted(names)) + + def test_mesos_monitor_get_metric_unit(self): + mesos_metric_spec = 'magnum.conductor.mesos_monitor.MesosMonitor.' \ + 'metrics_spec' + with mock.patch(mesos_metric_spec, + new_callable=mock.PropertyMock) as mock_mesos_metric: + mock_mesos_metric.return_value = self.test_metrics_spec + unit = self.mesos_monitor.get_metric_unit('metric1') + self.assertEqual('metric1_unit', unit) + + def test_mesos_monitor_compute_memory_util(self): + test_data = { + 'mem_total': 100, + 'mem_used': 50 + } + self.mesos_monitor.data = test_data + mem_util = self.mesos_monitor.compute_memory_util() + self.assertEqual(50, mem_util) + + test_data = { + 'mem_total': 0, + 'pods': 0, + } + self.mesos_monitor.data = test_data + mem_util = self.mesos_monitor.compute_memory_util() + self.assertEqual(0, mem_util)