Automatic discovery of TripleO Overcloud hardware

-geting IP addresses from Undercloud nova, allowing
 to poll all Overcloud nodes via SNMP
-adding support of basic auth, user_name and password
 used in TripleO by default

Change-Id: I189dbba9579055c8a1a878a769760a72e9174c6d
This commit is contained in:
Ladislav Smola 2014-05-06 15:34:12 +02:00
parent 1f87c36a67
commit 670736a2b6
6 changed files with 93 additions and 15 deletions

View File

@ -0,0 +1,62 @@
# -*- encoding: utf-8 -*-
#
# 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.config import cfg
from ceilometer import nova_client
from ceilometer.openstack.common.gettextutils import _
from ceilometer.openstack.common import log
from ceilometer import plugin
LOG = log.getLogger(__name__)
OPTS = [
cfg.StrOpt('url_scheme',
default='snmp://',
help='URL scheme to use for hardware nodes'),
cfg.StrOpt('readonly_user_name',
default='ro_snmp_user',
help='SNMPd user name of all nodes running in the cloud.'),
cfg.StrOpt('readonly_user_password',
default='password',
help='SNMPd password of all the nodes running in the cloud'),
]
cfg.CONF.register_opts(OPTS, group='hardware')
class NodesDiscoveryTripleO(plugin.DiscoveryBase):
def __init__(self):
super(NodesDiscoveryTripleO, self).__init__()
self.nova_cli = nova_client.Client()
def discover(self, param=None):
"""Discover resources to monitor."""
instances = self.nova_cli.instance_get_all()
ip_addresses = []
for instance in instances:
try:
ip_address = instance.addresses['ctlplane'][0]['addr']
final_address = (
cfg.CONF.hardware.url_scheme +
cfg.CONF.hardware.readonly_user_name + ':' +
cfg.CONF.hardware.readonly_user_password + '@' +
ip_address)
ip_addresses.append(final_address)
except KeyError:
LOG.error(_("Couldn't obtain IP address of"
"instance %s") % instance.id)
return ip_addresses

View File

@ -18,7 +18,6 @@
"""Inspector for collecting data over SNMP""" """Inspector for collecting data over SNMP"""
from pysnmp.entity.rfc3413.oneliner import cmdgen from pysnmp.entity.rfc3413.oneliner import cmdgen
from six.moves.urllib import parse as urlparse
from ceilometer.hardware.inspector import base from ceilometer.hardware.inspector import base
@ -73,9 +72,8 @@ class SNMPInspector(base.Inspector):
_interface_received_oid = "1.3.6.1.2.1.2.2.1.10" _interface_received_oid = "1.3.6.1.2.1.2.2.1.10"
_interface_transmitted_oid = "1.3.6.1.2.1.2.2.1.16" _interface_transmitted_oid = "1.3.6.1.2.1.2.2.1.16"
_interface_error_oid = "1.3.6.1.2.1.2.2.1.20" _interface_error_oid = "1.3.6.1.2.1.2.2.1.20"
# Default port and security name # Default port
_port = 161 _port = 161
_security_name = 'public'
def __init__(self): def __init__(self):
super(SNMPInspector, self).__init__() super(SNMPInspector, self).__init__()
@ -88,7 +86,8 @@ class SNMPInspector(base.Inspector):
else: else:
func = self._cmdGen.nextCmd func = self._cmdGen.nextCmd
ret_func = lambda x: x ret_func = lambda x: x
ret = func(cmdgen.CommunityData(self._get_security_name(host)),
ret = func(self._get_auth_strategy(host),
cmdgen.UdpTransportTarget((host.hostname, cmdgen.UdpTransportTarget((host.hostname,
host.port or self._port)), host.port or self._port)),
oid) oid)
@ -100,6 +99,15 @@ class SNMPInspector(base.Inspector):
else: else:
return ret_func(data) return ret_func(data)
def _get_auth_strategy(self, host):
if host.password:
auth_strategy = cmdgen.UsmUserData(host.username,
authKey=host.password)
else:
auth_strategy = cmdgen.CommunityData(host.username or 'public')
return auth_strategy
def _get_value_from_oid(self, oid, host): def _get_value_from_oid(self, oid, host):
return self._get_or_walk_oid(oid, host, True) return self._get_or_walk_oid(oid, host, True)
@ -186,10 +194,6 @@ class SNMPInspector(base.Inspector):
error=int(error)) error=int(error))
yield (interface, stats) yield (interface, stats)
def _get_security_name(self, host):
options = urlparse.parse_qs(host.query)
return options.get('security_name', [self._security_name])[-1]
def _get_ip_for_interface(self, host, interface_id): def _get_ip_for_interface(self, host, interface_id):
ip_addresses = self._walk_oid(self._interface_ip_oid, host) ip_addresses = self._walk_oid(self._interface_ip_oid, host)
for ip in ip_addresses: for ip in ip_addresses:

View File

@ -131,6 +131,14 @@ class Client(object):
detailed=True, detailed=True,
search_opts=search_opts)) search_opts=search_opts))
@logged
def instance_get_all(self):
"""Returns list of all instances."""
search_opts = {'all_tenants': True}
return self.nova_client.servers.list(
detailed=True,
search_opts=search_opts)
@logged @logged
def floating_ip_get_all(self): def floating_ip_get_all(self):
"""Returns all floating ips.""" """Returns all floating ips."""

View File

@ -214,13 +214,6 @@ class TestSNMPInspector(Base, test_base.BaseTestCase):
self.useFixture(mockpatch.PatchObject( self.useFixture(mockpatch.PatchObject(
self.inspector._cmdGen, 'nextCmd', new=faux_nextCmd)) self.inspector._cmdGen, 'nextCmd', new=faux_nextCmd))
def test_get_security_name(self):
self.assertEqual(self.inspector._get_security_name(self.host),
self.inspector._security_name)
host2 = network_utils.urlsplit("snmp://foo:80?security_name=fake")
self.assertEqual(self.inspector._get_security_name(host2),
'fake')
def test_get_cmd_error(self): def test_get_cmd_error(self):
self.useFixture(mockpatch.PatchObject( self.useFixture(mockpatch.PatchObject(
self.inspector, '_memory_total_oid', new='failure')) self.inspector, '_memory_total_oid', new='failure'))

View File

@ -96,6 +96,16 @@ class TestNovaClient(test.BaseTestCase):
self.assertEqual(11, instances[0].kernel_id) self.assertEqual(11, instances[0].kernel_id)
self.assertEqual(21, instances[0].ramdisk_id) self.assertEqual(21, instances[0].ramdisk_id)
def test_instance_get_all(self):
with mock.patch.object(self.nv.nova_client.servers, 'list',
side_effect=self.fake_servers_list):
instances = self.nv.instance_get_all()
self.assertEqual(2, len(instances))
self.assertEqual(42, instances[0].id)
self.assertEqual(1, instances[0].flavor['id'])
self.assertEqual(1, instances[0].image['id'])
@staticmethod @staticmethod
def fake_servers_list_unknown_flavor(*args, **kwargs): def fake_servers_list_unknown_flavor(*args, **kwargs):
a = mock.MagicMock() a = mock.MagicMock()

View File

@ -81,6 +81,7 @@ ceilometer.discover =
ipsec_connections = ceilometer.network.services.discovery:IPSecConnectionsDiscovery ipsec_connections = ceilometer.network.services.discovery:IPSecConnectionsDiscovery
fw_services = ceilometer.network.services.discovery:FirewallDiscovery fw_services = ceilometer.network.services.discovery:FirewallDiscovery
fw_policy = ceilometer.network.services.discovery:FirewallPolicyDiscovery fw_policy = ceilometer.network.services.discovery:FirewallPolicyDiscovery
tripleo_overcloud_nodes = ceilometer.hardware.discovery:NodesDiscoveryTripleO
ceilometer.poll.compute = ceilometer.poll.compute =
disk.read.requests = ceilometer.compute.pollsters.disk:ReadRequestsPollster disk.read.requests = ceilometer.compute.pollsters.disk:ReadRequestsPollster