2 #Copyright (C) 2009-2010 :
3 # Gabes Jean, naparuba@gmail.com
4 # Gerhard Lausser, Gerhard.Lausser@consol.de
5 # Gregory Starck, g.starck@gmail.com
7 #This file is part of Shinken.
9 #Shinken is free software: you can redistribute it and/or modify
10 #it under the terms of the GNU Affero General Public License as published by
11 #the Free Software Foundation, either version 3 of the License, or
12 #(at your option) any later version.
14 #Shinken is distributed in the hope that it will be useful,
15 #but WITHOUT ANY WARRANTY; without even the implied warranty of
16 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 #GNU Affero General Public License for more details.
19 #You should have received a copy of the GNU Affero General Public License
20 #along with Shinken. If not, see <http://www.gnu.org/licenses/>.
22 #sudo nmap 192.168.0.1 -T4 -O --traceroute -oX toto.xml
34 # If importing shinken fails, try to load from current directory
35 # or parent directory to support running without installation.
36 # Submodules will then be loaded from there, too.
38 if not hasattr(os
, "getuid") or os
.getuid() != 0:
39 imp
.load_module('shinken', *imp
.find_module('shinken', [".", ".."]))
41 from shinken
.log
import logger
42 from shinken
.objects
import *
43 from shinken
.macroresolver
import MacroResolver
48 parser
= optparse
.OptionParser(
49 "%prog [options] -c discovery_config -o config_output -m list of macros",
50 version
="%prog " + VERSION
)
51 parser
.add_option('-c', '--cfg-input',
52 dest
="cfg_input", help=('Discovery configuration file (discovery.cfg)'))
53 parser
.add_option('-o', '--dir-output', dest
="output_dir",
54 help="Directory output for results")
55 parser
.add_option('-w', '--overright', dest
="overright", action
='store_true',
56 help="Allow overwithing xisting file (disabled by default)")
57 parser
.add_option('-r', '--runners', dest
="runners",
58 help="List of runners you allow to run, (like nmap,vsphere)")
59 parser
.add_option('-m', '--macros', dest
="macros",
60 help="List of macros (like NMAPTARGETS=192.168.0.0/24). Should be the last argument")
64 opts
, args
= parser
.parse_args()
69 if not opts
.cfg_input
:
70 parser
.error("Requires a discovery configuration file, dsicovery.cfg (option -c/--cfg-input")
71 if not opts
.output_dir
:
72 parser
.error("Requires one output directory (option -o/--dir-output")
73 if not opts
.overright
:
76 overright
= opts
.overright
80 runners
= opts
.runners
.split(',')
82 raw_macros
.append(opts
.macros
)
84 raw_macros
.extend(args
)
86 print "Macros", raw_macros
91 print "The macro '%s' is malformed. I bail out" % m
93 macros
.append( (elts
[0], '='.join(elts
[1:])))
95 print "Got macros", macros
97 # Says if a host is up or not
99 status
= h
.find('status')
100 state
= status
.attrib
['state']
104 class ConfigurationManager
:
105 def __init__(self
, h
, path
, criticity
):
107 self
.hosts_path
= os
.path
.join(path
, 'hosts')
108 self
.srvs_path
= os
.path
.join(path
, 'services')
109 self
.templates
= ['generic-host']
111 self
.criticity
= criticity
115 # We search if our potential parent is present in the
116 # other detected hosts. If so, set it as my parent
117 def look_for_parent(self
, all_hosts
):
118 parent
= self
.h
.parent
119 print "Look for my parent", self
.h
.get_name(), "->", parent
120 # Ok, we didn't find any parent
125 print "Is it you?", h
.get_name()
126 if h
.get_name() == parent
:
127 print "Houray, we find our parent", self
.h
.get_name(), "->", h
.get_name()
128 self
.parents
.append(h
.get_name())
132 def fill_system_conf(self
):
135 #Ok, unknown os... not good
139 map = {('Windows', '2000') : 'windows',
140 ('Windows', '2003') : 'windows',
141 ('Windows', '7') : 'windows',
142 ('Windows', 'XP') : 'windows',
143 # ME? you are a stupid moron!
144 ('Windows', 'Me') : 'windows',
145 ('Windows', '2008') : 'windows',
146 # that's a good boy :)
147 ('Linux', '2.6.X') : 'linux',
148 ('Linux', '2.4.X') : 'linux',
149 # HPUX? I think you didn't choose...
150 ('HP-UX', '11.X') : 'hpux',
151 ('HP-UX', '10.X') : 'hpux',
155 print "Unknown OS:", ios
159 self
.templates
.append(t
)
161 # Look for VMWare VM or hosts
162 if self
.h
.is_vmware_vm():
163 self
.templates
.append('vmware-vm')
165 if self
.h
.is_vmware_esx():
166 self
.templates
.append('vmware-host')
169 def get_cfg_for_host(self
):
171 props
['host_name'] = self
.h
.get_name()
172 props
['criticity'] = self
.criticity
174 # Parents if we got some
175 if self
.parents
!= []:
176 props
['parents'] = ','.join(self
.parents
)
179 props
['use'] = ','.join(self
.templates
)
181 print "Want to write", props
182 s
= 'define host {\n'
185 s
+= ' %s %s\n' % (k
, v
)
191 def get_cfg_for_services(self
):
193 print "And now services:"
194 for srv
in self
.services
:
195 desc
= srv
['service_description']
196 s
= 'define service {\n'
199 s
+= ' %s %s\n' % (k
, v
)
207 def fill_ports_services(self
):
208 for p
in self
.h
.open_ports
:
209 print "The port", p
, " is open"
210 f
= getattr(self
, 'gen_srv_'+str(p
), None)
214 def fill_system_services(self
):
215 for t
in self
.templates
:
216 print "Registering services for the template", t
217 # Python functions cannot be -, so we change it by _
218 t
= t
.replace('-','_')
219 print "Search for", 'gen_srv_'+str(t
)
220 f
= getattr(self
, 'gen_srv_'+str(t
), None)
225 def generate_service(self
, desc
, check
):
226 srv
= {'use' : 'generic-service', 'service_description' : desc
, 'check_command' : check
, 'host_name' : self
.h
.get_name()}
227 self
.services
.append(srv
)
230 ######### For network ones
233 def gen_srv_80(self
):
234 self
.generate_service('HTTP', 'check_http')
237 def gen_srv_22(self
):
238 self
.generate_service('SSH', 'check_ssh')
240 # HTTPS + certificate
241 def gen_srv_443(self
):
242 self
.generate_service('HTTPS', 'check_https')
243 self
.generate_service('HTTPS-CERT', 'check_https_certificate')
246 def gen_srv_21(self
):
247 self
.generate_service('FTP', 'check_ftp')
250 def gen_srv_53(self
):
251 self
.generate_service('DNS', 'check_dig!$HOSTADDRESS$')
254 def gen_srv_1521(self
):
255 self
.generate_service('Oracle-Listener', 'check_oracle_listener')
258 def gen_srv_1433(self
):
259 self
.generate_service('MSSQL-Connexion', 'check_mssql_connexion')
260 print "I will need check_mssql_health from http://labs.consol.de/nagios/check_mssql_health/"
263 def gen_srv_25(self
):
264 self
.generate_service('SMTP', 'check_smtp')
267 def gen_srv_465(self
):
268 self
.generate_service('SMTPS', 'check_smtps')
271 def gen_srv_389(self
):
272 self
.generate_service('Ldap', 'check_ldap')
275 def gen_srv_636(self
):
276 self
.generate_service('Ldaps', 'check_ldaps')
279 def gen_srv_3306(self
):
280 self
.generate_service('Mysql', 'check_mysql_connexion')
286 def gen_srv_linux(self
):
287 print "You want a Linux check, but I don't know what to propose, sorry..."
289 def gen_srv_windows(self
):
290 print "You want a Windows check, but I don't know what to propose, sorry..."
292 #For a VM we can add cpu, io, mem and net
293 def gen_srv_vmware_vm(self
):
294 self
.generate_service('VM-Cpu', "check_esx_vm!cpu")
295 self
.generate_service('VM-IO', "check_esx_vm!io")
296 self
.generate_service('VM-Memory', "check_esx_vm!mem")
297 self
.generate_service('VM-Network', "check_esx_vm!net")
298 print "I will need the check_esx3.pl from http://www.op5.org/community/plugin-inventory/op5-projects/op5-plugins"
300 # Quite the same for the host
301 def gen_srv_vmware_host(self
):
302 self
.generate_service('ESX-host-Cpu', "check_esx_host!cpu")
303 self
.generate_service('ESX-host-IO', "check_esx_host!io")
304 self
.generate_service('ESX-host-Memory', "check_esx_host!mem")
305 self
.generate_service('ESX-host-Network', "check_esx_host!net")
306 print "I will need the check_esx3.pl from http://www.op5.org/community/plugin-inventory/op5-projects/op5-plugins"
310 # Write the host cfg file
311 def write_host_configuration(self
):
312 name
= self
.h
.get_name()
313 # If the host is bad, get out
317 # Write the directory with the host config
318 p
= os
.path
.join(self
.hosts_path
, name
)
319 print "I want to create", p
323 # If directory already exists, it's not a problem
324 if not exp
.errno
!= '17':
325 print "Cannot create the directory '%s' : '%s'" % (p
, exp
)
327 cfg_p
= os
.path
.join(p
, name
+'.cfg')
328 print "I want to write", cfg_p
329 s
= self
.get_cfg_for_host()
331 fd
= open(cfg_p
, 'w')
333 print "Cannot create the file '%s' : '%s'" % (cfg_p
, exp
)
339 def write_services_configuration(self
):
340 name
= self
.h
.get_name()
341 # If the host is bad, get out
345 # Write the directory with the host config
346 p
= os
.path
.join(self
.srvs_path
, name
)
347 print "I want to create", p
351 # If directory already exist, it's not a problem
352 if not exp
.errno
!= '17':
353 print "Cannot create the directory '%s' : '%s'" % (p
, exp
)
355 # Ok now we get the services to create
356 r
= c
.get_cfg_for_services()
358 cfg_p
= os
.path
.join(p
, name
+'-'+s
+'.cfg')
359 print "I want to write", cfg_p
362 fd
= open(cfg_p
, 'w')
364 print "Cannot create the file '%s' : '%s'" % (cfg_p
, exp
)
371 class DiscoveryMerger
:
372 def __init__(self
, path
, output_dir
, macros
, overright
, runners
):
375 self
.overright
= overright
376 self
.runners
= runners
377 self
.output_dir
= output_dir
378 self
.log
.load_obj(self
)
379 self
.config_files
= [path
]
381 self
.conf
.read_config(self
.config_files
)
382 buf
= self
.conf
.read_config(self
.config_files
)
384 # Add macros on the end of the buf so they will
385 # overright the resource.cfg ones
386 for (m
, v
) in macros
:
387 buf
+= '\n$%s$=%s\n' % (m
, v
)
389 raw_objects
= self
.conf
.read_config_buf(buf
)
390 self
.conf
.create_objects_for_type(raw_objects
, 'arbiter')
391 self
.conf
.create_objects_for_type(raw_objects
, 'module')
392 self
.conf
.early_arbiter_linking()
393 self
.conf
.create_objects(raw_objects
)
394 #self.conf.instance_id = 0
395 #self.conf.instance_name = ''
396 self
.conf
.linkify_templates()
397 self
.conf
.apply_inheritance()
399 self
.conf
.create_reversed_list()
400 self
.conf
.remove_twins()
401 self
.conf
.apply_implicit_inheritance()
402 self
.conf
.fill_default()
403 self
.conf
.clean_useless()
404 self
.conf
.pythonize()
406 self
.conf
.apply_dependancies()
407 #self.conf.explode_global_conf()
408 #self.conf.propagate_timezone_option()
409 #self.conf.create_business_rules()
410 #self.conf.create_business_rules_dependencies()
411 self
.conf
.is_correct()
413 self
.discoveryrules
= self
.conf
.discoveryrules
414 self
.discoveryruns
= self
.conf
.discoveryruns
416 #self.confs = self.conf.cut_into_parts()
417 #self.dispatcher = Dispatcher(self.conf, self.me)
419 #scheddaemon = Shinken(None, False, False, False, None)
420 #self.sched = Scheduler(scheddaemon)
422 #scheddaemon.sched = self.sched
426 #self.sched.load_conf(self.conf)
427 #e = ExternalCommandManager(self.conf, 'applyer')
428 #self.sched.external_command = e
429 #e.load_scheduler(self.sched)
430 #e2 = ExternalCommandManager(self.conf, 'dispatcher')
431 #e2.load_arbiter(self)
432 #self.external_command_dispatcher = e2
433 #self.sched.schedule()
435 # Hash = name, and in it (key, value)
437 # Hash = name, and in it rules that apply
438 self
.disco_matches
= {}
444 # Look if the name is a IPV4 address or not
445 def is_ipv4_addr(self
, name
):
446 p
= r
"^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$"
447 return (re
.match(p
, name
) is not None)
450 def read_disco_buf(self
):
451 buf
= self
.raw_disco_data
452 for l
in buf
.split('\n'):
454 # If it's not a disco line, bypass it
455 if not re
.search('::', l
):
460 #print "Bad discovery data"
462 name
= elts
[0].strip()
464 # We can choose to keep only the basename
465 # of the nameid, so strip the fqdn
466 # But not if it's a plain ipv4 addr
467 if self
.conf
.strip_idname_fqdn
:
468 if not self
.is_ipv4_addr(name
):
469 name
= name
.split('.', 1)[0]
471 data
= '::'.join(elts
[1:])
476 if not name
in self
.disco_data
:
477 self
.disco_data
[name
] = []
480 #print "Bad discovery data"
482 elts
= data
.split('=')
484 #print "Bad discovery data"
486 key
= elts
[0].strip()
487 value
= elts
[1].strip()
488 print "-->", name
, key
, value
489 self
.disco_data
[name
].append((key
, value
))
492 def match_rules(self
):
493 for name
in self
.disco_data
:
494 datas
= self
.disco_data
[name
]
495 for r
in self
.discoveryrules
:
496 if r
.is_matching_disco_datas(datas
):
497 if name
not in self
.disco_matches
:
498 self
.disco_matches
[name
] = []
499 self
.disco_matches
[name
].append(r
)
500 print "Generating", name
, r
.writing_properties
503 def is_allowing_runners(self
, name
):
506 # If we got no value, it's * by default
507 if '*' in self
.runners
:
511 #If we match the name, ok
512 for r
in self
.runners
:
514 # print "Look", r_name, name
518 # Not good, so not run this!
521 def allowed_runners(self
):
522 return [r
for r
in self
.discoveryruns
if self
.is_allowing_runners(r
.get_name())]
524 def launch_runners(self
):
525 for r
in self
.allowed_runners():
526 print "I'm launching", r
.get_name()
527 print r
.discoveryrun_command
531 def wait_for_runners_ends(self
):
535 for r
in self
.allowed_runners():
536 if not r
.is_finished():
537 #print "Check finished of", r.get_name()
541 #print r.get_name(), "is not finished"
546 def get_runners_outputs(self
):
547 self
.raw_disco_data
= '\n'.join(r
.get_output() for r
in self
.allowed_runners() if r
.is_finished())
548 print "Got Raw disco data", self
.raw_disco_data
551 # Write all configuration we've got
552 def write_config(self
):
553 for name
in self
.disco_data
:
554 print "Writing", name
, "configuration"
555 self
.write_host_config(name
)
556 self
.write_service_config(name
)
559 # We search for all rules of type host, and we merge them
560 def write_host_config(self
, host
):
562 for (name
, rules
) in self
.disco_matches
.items():
565 rs
= [r
for r
in rules
if r
.creation_type
== 'host']
566 host_rules
.extend(rs
)
568 # If no rule, bail out
569 if len(host_rules
) == 0:
573 d
= {'host_name' : host
}
575 d
.update(r
.writing_properties
)
576 print "Will generate", d
577 self
.write_host_config_to_file(host
, d
)
580 # Will wrote all properties/values of d for the host
582 def write_host_config_to_file(self
, host
, d
):
583 p
= os
.path
.join(self
.output_dir
, host
)
584 print "Want to create host path", p
588 # If directory already exist, it's not a problem
589 if not exp
.errno
!= '17':
590 print "Cannot create the directory '%s' : '%s'" % (p
, exp
)
592 cfg_p
= os
.path
.join(p
, host
+'.cfg')
593 if os
.path
.exists(cfg_p
) and not overright
:
594 print "The file '%s' already exists" % cfg_p
597 buf
= self
.get_cfg_bufer(d
, 'host')
599 # Ok, we create it so (or overright)
601 fd
= open(cfg_p
, 'w')
605 print "Cannot create the file '%s' : '%s'" % (cfg_p
, exp
)
609 # Generate all service for a host
610 def write_service_config(self
, host
):
612 for (name
, rules
) in self
.disco_matches
.items():
615 rs
= [r
for r
in rules
if r
.creation_type
== 'service']
618 if 'service_description' in r
.writing_properties
:
619 desc
= r
.writing_properties
['service_description']
620 if not desc
in srv_rules
:
622 srv_rules
[desc
].append(r
)
624 #print "Generate services for", host
626 for (desc
, rules
) in srv_rules
.items():
627 d
= {'service_description' : desc
, 'host_name' : host
}
629 d
.update(r
.writing_properties
)
630 print "Generating", desc
, d
631 self
.write_service_config_to_file(host
, desc
, d
)
634 # Will wrote all properties/values of d for the host
636 def write_service_config_to_file(self
, host
, desc
, d
):
637 p
= os
.path
.join(self
.output_dir
, host
)
639 # The host conf should already exist
640 cfg_host_p
= os
.path
.join(p
, host
+'.cfg')
641 if not os
.path
.exists(cfg_host_p
):
642 print "No host configuration available, I bail out"
645 cfg_p
= os
.path
.join(p
, desc
+'.cfg')
646 if os
.path
.exists(cfg_p
) and not overright
:
647 print "The file '%s' already exists" % cfg_p
650 buf
= self
.get_cfg_bufer(d
, 'service')
652 # Ok, we create it so (or overright)
654 fd
= open(cfg_p
, 'w')
658 print "Cannot create the file '%s' : '%s'" % (cfg_p
, exp
)
662 # Create a define t { } with data in d
663 def get_cfg_bufer(self
, d
, t
):
664 tab
= ['define %s {' % t
]
665 for (key
, value
) in d
.items():
666 tab
.append(' %s %s' % (key
, value
))
668 return '\n'.join(tab
)
674 cfg_input
= opts
.cfg_input
675 output_dir
= opts
.output_dir
676 overright
= opts
.overright
677 d
= DiscoveryMerger(cfg_input
, output_dir
, macros
, overright
, runners
)
683 d
.wait_for_runners_ends()
684 d
.get_runners_outputs()
690 #print d.disco_matches