#!/usr/bin/python
#
# This is server-audit script for Racktables project
# It discover system, import or update information into racktables database
# 
# Script support following information
#   * hostname
#    * service tag
#    * supermicro exeption for service tag (same service tag and express servicetag for all machines)
#    * for Dell servers it retrieve support type and support expiration date
#    * Physical and logical interfaces (eth,bond,bridges)
#    * IPv4 and IPv6 IP addresses, import and update in database
#    * Dell Drack IP address (require Dell-OMSA Installed)
#    * OS Dristribution and Release information
#    * HW vendor and product type
#    * Hypervisor recognition (Xen 4.x)
#    * Virtual server recognition (Xen 4.x)
#    * Link Virtual server with hypervisor as container in Racktables
#    * Racktables logging - when change ip addresses or virtual link with hypervisor
#
#    This utility is released under GPL v2
#    
#    Server Audit utility for Racktables Datacenter management project.
#    Copyright (C) 2012  Robert Vojcik (robert@vojcik.net)
#    
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of the GNU General Public License
#    as published by the Free Software Foundation; either version 2
#    of the License, or (at your option) any later version.
#    
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
from __future__ import print_function
import sys
import os
import platform
from configparser import ConfigParser
import time
import datetime
import re
import random
import shutil
import argparse
import ipaddr
import MySQLdb
from rt_server_client.ToolBox import net, base
from rt_server_client import sysinfo
from rt_server_client.ToolBox import init as database_init
import rtapi

config_path = "/etc/rt-server-client/"
motd_file_original = "/etc/motd.ls.orig"
motd_file = "/etc/motd"

# Default config file
main_config = "main.conf"

##
## Parsing arguments
##
parser = argparse.ArgumentParser(
    description= 'Server audit automation system for RackTables',
    epilog='Created by Robert Vojcik <robert@vojcik.net>')

parser.add_argument("-f", dest="force_init", default=False, action="store_true", help="Force initialization, automaticly rewrite hostname")
parser.add_argument("-d", dest="debug_mode", default=False, action="store_true", help="Debug mode")
parser.add_argument("--init", dest="init_mode", default=False, action="store_true", help="Inicialization of objects in DB")
parser.add_argument("--version", dest="version_print", default=False, action="store_true", help="Print version")

args = parser.parse_args()
##
## END Parsing arguments
##

# Debug mode
debug = base.Debug(args)

if args.version_print:
    import rt_server_client
    print(rt_server_client.__version__)
    sys.exit(0)

try:
    debug.print_message("Opening config: "+config_path+main_config)
    config_file = open(config_path + main_config)
except IOError as e:
    print("({})".format(e))
    sys.exit()


# Parsing config options
config = ConfigParser()
config.readfp(config_file)

# Close config
config_file.close()

# Sleep for some seconds. When running on 200 hosts in same second, local dns should have a problem with it
if not args.debug_mode:
    time.sleep(random.randint(1,12))

############################################################
##                                                        ##
##                 Main Database part                     ##
##                                                        ##
############################################################

# Create connection to database
try:
    # Create connection to database
    debug.print_message("Database connection [host, port, passwd, database, user] = [ %s, %s, %s, %s, %s ]" % (config.get('mysqldb','host'),"3306", "xxxxxx",config.get('mysqldb','db'),config.get('mysqldb','user')))
    db = MySQLdb.connect(host=config.get('mysqldb','host'),port=3306, passwd=config.get('mysqldb','password'),db=config.get('mysqldb','db'),user=config.get('mysqldb','user'))
except MySQLdb.Error as e:
    print("Error %d: %s" % (e.args[0],e.args[1]))
    sys.exit(1)


# Initialize rtapi object
debug.print_message("Initializing RT Api object")
rtobject = rtapi.RTObject(db)

# Initialization mode
if args.init_mode:
    debug.print_message("Running rt-server-client initialization")
    ret = database_init.run(rtobject)
    if not ret:
        base.perr("Initialization process failed")
        sys.exit(1)
    else:
        base.pok("Initialization process done")
        sys.exit(0)

# Prepare server_object and gather information about the system
server_object = sysinfo.SysInfo(args)
server_object.DiscoverAll()
debug.print_message("Discovered info from sysinfo class:" + str(server_object.information))

##
## Here insertion into database starts
##
debug.print_message("STARTING MAIN DATABASE INSERTION/UPDATE")
if not rtobject.ObjectExistST(server_object.information['service_tag']):
    #
    # service tag not exist
    # 
    debug.print_message("Service tag not exist in database")
    if not rtobject.ObjectExistName(server_object.information['hostname']):
        # hostname not exist & ST not exist = insert new server Object
        rtobject.AddObject(
            server_object.information['hostname'],
            server_object.information['server_type_id'],
            server_object.information['service_tag'],
            server_object.information['label']
        )
        object_id = rtobject.GetObjectId(server_object.information['hostname'])
        debug.print_message("Added new object to database %s with ID %s" % (
            str([
            server_object.information['hostname'],
            server_object.information['server_type_id'],
            server_object.information['service_tag'],
            server_object.information['label']
            ]),
            str(object_id)
        ))
    else:
        #
        # hostname exist, what to do?
        #
        print("Hostname %s already exist but with different service tag. I'm using service tag: %s" % (
            server_object.information['hostname'],
            server_object.information['service_tag']
        ))
        debug.print_message("Hostname %s already exist. My Service Tag is %s" % (
            server_object.information['hostname'],
            server_object.information['service_tag']
        ))
        sys.exit(1)
else:
    #
    # service tag exist
    #
    #check if hostname is the same
    if (rtobject.ObjectExistName(server_object.information['hostname'])) or (args.force_init) :

        if args.force_init:
            debug.print_message("Force initialization enabled")
            debug.print_message("Updating hostname")
            rtobject.UpdateObjectName(rtobject.GetObjectId(rtobject.GetObjectNameByAsset(server_object.information['service_tag'])), server_object.information['hostname'])
        else:
            # hostname exist and service tag is same, update info
            debug.print_message("Hostname already in DB, service tag is matching. Updating information in DB")

        if rtobject.ObjectExistSTName(server_object.information['hostname'], server_object.information['service_tag']):
            object_id = rtobject.GetObjectId(server_object.information['hostname'])
            object_label = rtobject.GetObjectLabel(object_id)
            debug.print_message("Object id is: %s and label %s" %(str(object_id), object_label))
        else:
            print("Can't do it. Hostname %s and service-tag %s already exist, but does not belong to the same server"% (
                server_object.information['hostname'],
                server_object.information['service_tag']
            ))
            debug.print_message("Can't do it. Hostname %s and service-tag %s already exist, but does not belong to the same server"% (
                server_object.information['hostname'],
                server_object.information['service_tag']
            ))
            sys.exit(1)

    else:
        #Hostname is different
        print("Service tag %s already exist, but hostname is different." % server_object.information['service_tag'])
        debug.print_message("Service tag %s already exist, but hostname is different." % server_object.information['service_tag'])
        sys.exit(1)
#
# Update Object information
#
# Update Zaloha attribute
if args.force_init:
    debug.print_message("Updating Zaloha attribute")
    rtobject.InsertAttribute(
        object_id,
        server_object.information['server_type_id'],
        rtobject.GetAttributeId("Zaloha"),
        'NULL',
        rtobject.GetDictionaryIdByValue('No', rtobject.GetDictionaryChapterId('Yes/No')),
    )

#Update label if it's not same
if server_object.information['update_label'] == "yes":
    if server_object.information['label'] != object_label:
        rtobject.UpdateObjectLabel(object_id, server_object.information['label'])
        logstring = "Label changed %s -> %s" % (object_label,server_object.information['label'])
        rtobject.InsertLog(object_id,logstring)
        debug.print_message("Updating old label ( %s ) with new ( %s )" %(object_label, server_object.information['label']))
    else:
        debug.print_message("Label is same, skip update")

# Set Installation date if it's blank
result = rtobject.GetAttributeValue(object_id, rtobject.GetAttributeId("Installation Date"))
debug.print_message("Installation date output: %s" % (str(result)))
if result == None:
    rtobject.InsertAttribute(
        object_id,
        server_object.information['server_type_id'],
        rtobject.GetAttributeId("Installation Date"),
        "NULL",
        int(time.time()))
    rtobject.InsertLog(object_id, "Server-Audit insert instalation date")
    debug.print_message("Installation date is blank, setting actual time")

# OS System Information
searchstring = server_object.information['os_distribution'] + "%" + server_object.information['os_codename']
os_id = rtobject.GetDictionaryId(searchstring)
attr_id = rtobject.GetAttributeId("SW type")
debug.print_message("SW Type, searching for: %s , Found: %s, Updating: %s" %(searchstring, str(os_id), str(attr_id)))

if os_id == None:
    # SW Type not fount, adding one
    debug.print_message("SW id not found in racktables, adding one.")
    sw_dictionary_string = "%s%%GPASS%%%s (%s)" % (server_object.information['os_distribution'], server_object.information['os_version'], server_object.information['os_codename'])
    debug.print_message("Inserting SW type: " + str(sw_dictionary_string))
    rtobject.InsertDictionaryValue(rtobject.GetDictionaryChapterId('server OS type'), str(sw_dictionary_string))
    os_id = rtobject.GetDictionaryId(searchstring)
    debug.print_message("SW Type, searching for: %s , Found: %s, Updating: %s" %(searchstring, str(os_id), str(attr_id)))

if os_id != None:
    debug.print_message("Inserting OS attribute: obj_id=%d srv_type_id=%d attr_id=%d os_id=%d hostname=%s" % (
        object_id,
        server_object.information['server_type_id'],
        attr_id,
        os_id,
        server_object.information['hostname']
    ))
    rtobject.InsertAttribute(
        object_id,
        server_object.information['server_type_id'],
        attr_id,
        "NULL",
        os_id,
    )
else:
    debug.print_message("OS id not found in racktables")

# Insert kernel version atributes
if server_object.information['kernel_version'] != "":
    attr_id = rtobject.GetAttributeId("Kernel")
    kernel_id=rtobject.GetDictionaryId(server_object.information['kernel_version'])
    if kernel_id == None:
        #Have to insert kernel version into dictionary
        debug.print_message("Inseting new kernel version to dictionary: %s"% str(server_object.information['kernel_version']))
        # kernels dictionary ahs ID 10003
        rtobject.InsertDictionaryValue(rtobject.GetDictionaryChapterId('kernels'),str(server_object.information['kernel_version']))
    kernel_id=rtobject.GetDictionaryId(server_object.information['kernel_version'])
    #log kernel change, and update RT
    if rtobject.GetAttributeValue(object_id, attr_id) != None:
        last_kernel_id = rtobject.GetAttributeValue(object_id, attr_id)[1]
        last_kernel_version = rtobject.GetDictionaryValueById(last_kernel_id)
    else:
        last_kernel_id = ""
        last_kernel_version = ""
    if last_kernel_id != kernel_id:
        logstring = "Kernel changed %s -> %s" % (last_kernel_version,server_object.information['kernel_version'])
        rtobject.InsertLog(object_id,logstring)
        rtobject.InsertAttribute(object_id,server_object.information['server_type_id'],attr_id,"NULL",kernel_id)

if server_object.information['server_type_id'] == 4:
    #get id of HW
    words = server_object.information['product_name'].split()
    searchstring = server_object.information['vendor'] + "%" + "%".join(str(x) for x in words)
    hw_id = rtobject.GetDictionaryId(searchstring)
    #insert os and hw info
    attr_id = rtobject.GetAttributeId("HW type")
    debug.print_message("HW Type, searching for: %s , Found: %s, Updating: %s" %(searchstring, str(hw_id), str(attr_id)))
    if hw_id == None:
        # HW Type not fount, adding one
        debug.print_message("HW id not found in racktables, adding one.")
        hw_dictionary_string = "%s%%GPASS%%%s" % (server_object.information['vendor'], server_object.information['product_name'])
        debug.print_message("Inserting HW type: " + str(hw_dictionary_string))
        rtobject.InsertDictionaryValue(rtobject.GetDictionaryChapterId('server models'), str(hw_dictionary_string))
        hw_id = rtobject.GetDictionaryId(searchstring)
        debug.print_message("HW Type, searching for: %s , Found: %s, Updating: %s" %(searchstring, str(hw_id), str(attr_id)))

    # Insert attribute to server
    if hw_id != None:
        debug.print_message("Inserting HW attribute: obj_id=%d srv_type_id=%d attr_id=%d hw_id=%d" % (
            object_id,
            server_object.information['server_type_id'],
            attr_id,
            hw_id,
        ))
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            attr_id,
            "NULL",
            hw_id,
        )
    else:
        debug.print_message("Unable to find HW type")
        
        
        
#TODO
    # Insert CPU and Memory Information
    debug.print_message("Inserting CPU attribute information")
    if server_object.information['cpu_model_name'] != "":
        debug.print_message("CPU Model Name")
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            rtobject.GetAttributeId("CPU Model"),
            server_object.information['cpu_model_name'],
            "NULL")

    if server_object.information['cpu_logical_num'] != "":
        debug.print_message("CPU logical num")
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            rtobject.GetAttributeId("CPUs Logical"),
            "NULL",
            server_object.information['cpu_logical_num'])

    if server_object.information['cpu_num'] != "":
        debug.print_message("CPU num")
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            rtobject.GetAttributeId("CPUs"),
            "NULL",
            server_object.information['cpu_num'])

    if server_object.information['cpu_cores'] != "":
        debug.print_message("CPU cores")
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            rtobject.GetAttributeId("Cores per CPU"),
            "NULL",
            server_object.information['cpu_cores'])

    if server_object.information['cpu_mhz'] != "":
        debug.print_message("CPU MHz")
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            rtobject.GetAttributeId("CPU, MHz"),
            "NULL",
            server_object.information['cpu_mhz'])

    if server_object.information['memory_mb'] != "":
        debug.print_message("Memory MB")
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            rtobject.GetAttributeId("RAM Mem, MB"),
            "NULL",
            server_object.information['memory_mb'])

    if server_object.information['hypervisor'] == "yes":
        searchstring = "Hypervisor"
        attid_hyp = rtobject.GetAttributeId(searchstring)
        debug.print_message("Hypervisor, searching for: %s , Found: %s" %(searchstring, str(attid_hyp)))
        rtobject.InsertAttribute(
            object_id,
            server_object.information['server_type_id'],
            attid_hyp,
            'NULL',
            rtobject.GetDictionaryIdByValue('Yes', rtobject.GetDictionaryChapterId('Yes/No')))

        if len(server_object.information['virtual_servers']) != 0:
            rtobject.CleanVirtuals(
                object_id,
                server_object.information['virtual_servers'])
            for virtual_name in server_object.information['virtual_servers']:
                virtual_id = rtobject.GetObjectId(virtual_name)
                if virtual_id != None:
                    debug.print_message("Virtual Linking id:%s with %s(%s)" %(str(object_id), str(virtual_id), str(virtual_name)))
                    rtobject.LinkVirtualHypervisor(object_id,virtual_id)

    if len(server_object.information['network']['drac_ip']) != 0:
        debug.print_message("Inserting Drac IP")
        rtobject.UpdateNetworkInterface(object_id,'drac')
        rtobject.InterfaceAddIpv4IP(object_id,'drac',server_object.information['network']['drac_ip'])

for device in server_object.information['network']['device_list']:
    #Add/update network interface
    port_id = rtobject.UpdateNetworkInterface(object_id,device)
    #Add network connections
    if server_object.information['network']['interface_connections'][server_object.information['network']['device_list'].index(device)] != '':
        rtobject.LinkNetworkInterface(
            object_id,
            device,
            server_object.information['network']['interface_connections'][server_object.information['network']['device_list'].index(device)][0],
            server_object.information['network']['interface_connections'][server_object.information['network']['device_list'].index(device)][1])
    #Add IPv4 ips
    if server_object.information['network']['interfaces_ips'][server_object.information['network']['device_list'].index(device)] != '':
        for ip in server_object.information['network']['interfaces_ips'][server_object.information['network']['device_list'].index(device)]:
            rtobject.InterfaceAddIpv4IP(object_id,device,ip)
    #Add IPv6 ips
    if server_object.information['network']['interfaces_ips6'][server_object.information['network']['device_list'].index(device)] != '':
        for ip in server_object.information['network']['interfaces_ips6'][server_object.information['network']['device_list'].index(device)]:
            rtobject.InterfaceAddIpv6IP(object_id,device,ip)

# Update motd from comment And Tags
comment = rtobject.GetObjectComment(object_id)
debug.print_message("Getting object comment: \n"+str(comment))

#Prepare tag comment
tags_list = rtobject.GetObjectTags(object_id)
debug.print_message("Retrieving TAG list: "+str(tags_list))
tag_array={}
comment_tag=''
for tag_row in tags_list:
        #tag_array[tag_row[0]]
    if not tag_array.get(tag_row[0]):
        tag_array[tag_row[0]] = []
    tag_array[tag_row[0]].append(tag_row[1])

for key in tag_array.keys():
    if key:
        comment_tag = comment_tag + key + ": "
    for tag in tag_array[key]:
        comment_tag = comment_tag + tag + " "
    comment_tag = comment_tag + "\n"
    debug.print_message("Tags\n"+comment_tag)

if comment == None:
    comment = ""
# copy template to motd, if no exist make tamplate from motd
if not os.path.isfile(motd_file):
    open(motd_file, 'a').close()

try:
    shutil.copy2(motd_file_original, motd_file)
except IOError :
    shutil.copy2(motd_file,motd_file_original)

debug.print_message("Updating MOTD file")
motd_open_file = open(motd_file, "a")
motd_open_file.write("\n\033[34m--- tags ---\033[33m\n\n" + comment_tag + "\033[0m")
motd_open_file.write("\n\033[34m--- comment ---\033[33m\n\n" + comment + "\033[0m\n")
motd_open_file.close()
