Hi Rafael,

I think this is a useful sample to have. I have some comments on the 
patch inline below.

/ Johan

On 11/18/2015 10:17 AM, Rafael Odzakow wrote:
> +#!/usr/bin/env python
> +""" Adds and removes IMM configuration for nodes """
> +
> +import re
> +import hashlib
> +import time
> +import subprocess
> +import os
> +import argparse
> +from pyosaf.utils import SafException, immom
> +from pyosaf.utils.immom.ccb import Ccb
> +from pyosaf.utils.immom.iterator import InstanceIterator as inst_iter
> +
> +RED2N = 1
> +NWAYACTIVE = 4
> +NORED = 5
Johan: These are defined as enums in saAmf.py. Can you use them instead 
of defining your own constants?
> +
> +ADMIN_UNLOCK = 1
> +ADMIN_LOCK = 2
> +ADMIN_LOCK_IN = 3
> +ADMIN_UNLOCK_IN = 4
Johan: These are defined as enums in saAmf.py. Can you use them instead?
> +
> +
> +def print_object(immobj, new_dn=None):
> +    """ Print an ImmObject and attributes with the type enum """
> +    if new_dn:
> +        print new_dn
> +    else:
> +        print immobj.dn
> +    for key, value in immobj.attrs.iteritems():
> +        val = ''
> +        if len(value[1]):
> +            val = value[1]
> +            if value[1][0]:
> +                val = value[1][0]
> +        if val:
> +            print '\t{0}: {1} : {2}'.format(key, val, value[0])
> +
> +
> +def get_rdn_attribute_for_class(class_name):
> +    """ Returns the RDN attribute for the given class. This is safe to call
> +        from OI callbacks
> +    """
> +    desc = immom.class_description_get(class_name)
> +    from pyosaf import saImm
Johan: Move this import to the top of the file.
> +    for attr_desc in desc:
> +        if attr_desc.attrFlags & saImm.saImm.SA_IMM_ATTR_RDN:
> +            return attr_desc.attrName
> +    return None
Johan: This would be useful for many IMM users. Can you move this to 
immom/__init__.py?
> +
> +
> +def find_object_type(class_name, parent_list):
> +    """ Find objects of a certain class under a parent list
> +
> +        return: list of ImmObjects
> +    """
> +    result = []
> +    for par in parent_list:
> +        for immobj in inst_iter(class_name, par.dn):
> +            result.append(immobj)
> +    return result
> +
> +
> +def find_node_dns(hostname):
> +    """ Returns the DNs of CLM and AMF node for the hostname
> +
> +        returns: AMF node DN and CLM node DN
> +    """
> +    for node in inst_iter('SaAmfNode'):
> +        # Get the first value part of the DN
> +        hname = node.saAmfNodeClmNode.split(',')[0].split('=')[1]
> +        if hname == hostname:
> +            return node.dn, node.saAmfNodeClmNode
> +    raise Exception('Could not find IMM object for hostname [%s]' % hostname)
> +
> +
> +def redundancy_of_su(su_dn):
> +    """ The redundancy of a SU
> +
> +        returns: saAmfSgtRedundancyModel attribute value
> +    """
> +    # Get the SG DN from the SU DN
> +    sg_of_su = su_dn.split(',', 1)[1]
> +    itsg = immom.get(sg_of_su)
> +    # Get the redundancy from the SG type
> +    itsg_t = immom.get(itsg.saAmfSGType)
> +    return itsg_t.saAmfSgtRedundancyModel
> +
> +
> +def find_sus(amfnode_dn):
> +    """ Find all SUs hosted by a AMF node matching some redundancy models
> +
> +        returns: list of SUs (ImmObject)
> +    """
> +    sus = []
> +    for itsu in inst_iter('SaAmfSU'):
> +        if amfnode_dn == itsu.saAmfSUHostedByNode:
> +            red = redundancy_of_su(itsu.dn)
> +            if red == NORED or red == NWAYACTIVE or red == RED2N:
> +                sus.append(itsu)
> +    return sus
> +
> +
> +def find_sis_apps(sus, redundancy):
> +    """ Match the SUs to SIs for a given redundancy model
> +
> +        returns: The matched SIs as list of ImmObjects
> +                 The apps of SUs as list of DNs
> +    """
> +    matched_sus = []
> +    matched_sgs = []
> +    matched_apps = []
> +    protected_sis = []
> +    su_svctypes = []
> +
> +    # Collect the SUs, SGs and Apps of a certain redundancy
> +    for itsu in sus:
> +        if redundancy_of_su(itsu.dn) == redundancy:
> +            matched_sus.append(itsu)
> +            # Get SG and App DNs from the SU DN
> +            matched_sgs.append(itsu.dn.split(',', 1)[1])
> +            matched_apps.append(itsu.dn.split(',', 2)[2])
> +
> +    # Keep the order and make the items unique
> +    matched_apps = sorted(set(matched_apps), key=matched_apps.index)
> +
> +    # Collect the SIs protected by the SGs
> +    for itsi in inst_iter('SaAmfSI'):
> +        if itsi.saAmfSIProtectedbySG in matched_sgs:
> +            protected_sis.append(itsi)
> +
> +    # Collect the SvcTypes of the SUs
> +    for itsu in matched_sus:
> +        su_type = immom.get(itsu.saAmfSUType)
> +        for svc_type in su_type.saAmfSutProvidesSvcTypes:
> +            su_svctypes.append(svc_type)
> +
> +    # Match SvcType in SU and SI
> +    result_sis = []
> +    for itsi in protected_sis:
> +        if itsi.saAmfSvcType in su_svctypes:
> +            result_sis.append(itsi)
> +
> +    return result_sis, matched_apps
> +
> +
> +def prepare_for_write(objects):
> +    """ Prepare objects for write by removing runtime attributes
> +
> +        returns: List of ImmObjects without runtime attributes
> +    """
> +    conf_objects = []
> +    for immobj in objects:
> +        immobj = immom.get(immobj.dn, ['SA_IMM_SEARCH_GET_CONFIG_ATTR'])
> +        del immobj.attrs['SaImmAttrAdminOwnerName']
> +        del immobj.attrs['SaImmAttrClassName']
> +        del immobj.attrs['SaImmAttrImplementerName']
> +        conf_objects.append(immobj)
> +    return conf_objects
> +
> +
> +def find_node_groups(amf_node):
> +    """ fetch all node groups a node belongs to """
> +    matching_groups = []
> +    for ngrp in inst_iter('SaAmfNodeGroup'):
> +        if str.find(''.join(ngrp.saAmfNGNodeList), amf_node) >= 0:
> +            matching_groups.append(ngrp)
> +    return matching_groups
> +
> +
> +def collect_objects(from_amfnode_dn, from_clmnode_dn):
Johan: Can you rename this function to make it more clear what it does 
(i.e. what objects it collects)?
> +    """ Read in all objects to be copied from the node.
> +
> +        returns: list of collected ImmObjects
> +    """
> +    sus = find_sus(from_amfnode_dn)
> +    comps = find_object_type('SaAmfComp', sus)
> +    hchks = find_object_type('SaAmfHealthcheck', comps)
> +    comps_cs = find_object_type('SaAmfCompCsType', comps)
> +    sis, _ = find_sis_apps(sus, NORED)
> +    csis = find_object_type('SaAmfCSI', sis)
> +    csiattrs = find_object_type('SaAmfCSIAttribute', csis)
> +
> +    swbundles = []
> +    for immobj in inst_iter('SaAmfNodeSwBundle', from_amfnode_dn):
> +        swbundles.append(immobj)
> +
> +    from_amfnode = immom.get(from_amfnode_dn)
> +    from_clmnode = immom.get(from_clmnode_dn)
> +
> +    # Order is important
> +    return [from_amfnode, from_clmnode] + sus + sis + csis + comps + \
> +        comps_cs + swbundles + hchks + csiattrs
> +
> +
> +def modify_object(immobj, suffix, new_amfnode_dn, new_hostname):
> +    """ Rename DN and change attributes for one IMM object """
> +    rename_str = None
> +    immobj.new_dn = immobj.dn
> +    match = re.search('safSu=[^,]*|safSi[^,]*', immobj.new_dn)
> +    if match:
> +        rename_str = immobj.new_dn[match.start():match.end()] + new_hostname
> +        rename_str += suffix
> +        rename_str = hashlib.md5(rename_str).hexdigest()[:10]
> +
> +    immobj.new_dn = re.sub(r'(safSu|safSi)=([^,]*),', r'\1={0},'.
> +                           format(rename_str), immobj.new_dn)
> +    immobj.new_dn = re.sub('safNode=[^,]*,', 'safNode={0},'.
> +                           format(new_hostname), immobj.new_dn)
> +    immobj.new_dn = re.sub('safAmfNode=[^,]*,', 'safAmfNode={0},'.
> +                           format(new_hostname), immobj.new_dn)
> +
> +    if immobj.class_name == 'SaAmfSU':
> +        immobj.saAmfSUHostNodeOrNodeGroup = new_amfnode_dn
> +        immobj.saAmfSUAdminState = ADMIN_UNLOCK
> +
> +    elif immobj.class_name == 'SaAmfNode':
> +        immobj.saAmfNodeAdminState = ADMIN_LOCK_IN
> +        immobj.saAmfNodeClmNode = re.sub(
> +            'safNode=[^,]*,', 'safNode={0},'.format(new_hostname),
> +            immobj.saAmfNodeClmNode)
> +
> +
> +def split_rdn_and_parent(immobj):
> +    """ Split the DN of the passed in object into RDN and parent DN
> +
> +        returns: RDN and parent DN as strings
> +    """
> +    rdn = immobj.new_dn.split(',', 1)[0]
> +    parent = immobj.new_dn.split(',', 1)[1]
> +    # Handle the case when we have two backslashes
> +    while rdn[-1] == '\\':
> +        parent = parent.split(',', 1)[1]
> +        rdn = parent.split(',', 1)[0]
> +    return rdn, parent
> +
> +
> +def lock_node(amfnode, clmnode):
Johan: Could this function be broken out as a start to AMF utils lib 
like pyosaf/utils/amf/__init__.py?
> +    """ Lock the AMF and CLM node """
> +    def admin_op_no_checks(obj, operation):
> +        """ Ignore exceptions """
> +        try:
> +            immom.admin_op_invoke(obj, operation)
> +        except SafException:
> +            pass
Johan: Would it make sense to break out this function to immom/__init__.py?

> +    admin_op_no_checks(amfnode.dn, ADMIN_LOCK)
> +    admin_op_no_checks(amfnode.dn, ADMIN_LOCK_IN)
> +    admin_op_no_checks(clmnode.dn, ADMIN_LOCK)
> +
> +    if amfnode.saAmfNodeAdminState != ADMIN_LOCK_IN:
> +        raise Exception('Could not lock-in the AmfNode')
> +    elif clmnode.saClmNodeAdminState != ADMIN_LOCK:
> +        raise Exception('Could not lock the ClmNode')
> +
> +
> +def scale_out(new_hostname, from_hostname):
> +    """ scale out one node
> +
> +        params:
> +            new_hostname - hostname of the new node
> +            from_hostname - hostname of the template node
> +    """
> +    print '-Scaling out to [%s] config copied from [%s]' % (new_hostname,
> +                                                            from_hostname)
> +    for itsu in inst_iter('SaAmfSU'):
> +        # Get the first value part of the DN
> +        hname = itsu.saAmfSUHostedByNode.split(',')[0].split('=')[1]
> +        if hname == new_hostname:
> +            print 'Node already has SUs, no scaling-out done'
> +            return 0
> +
> +    new_amfnode = new_hostname
> +    from_amfnode_dn, from_clmnode_dn = find_node_dns(from_hostname)
> +    new_amfnode_dn = re.sub('safAmfNode=[^,]*,', 'safAmfNode={0},'.
> +                            format(new_amfnode), from_amfnode_dn)
> +    objects = collect_objects(from_amfnode_dn, from_clmnode_dn)
> +    conf_objects = prepare_for_write(objects)
> +    #nodegroups_to_join = find_node_groups(from_amfnode_dn)
> +
> +    suffix = str(time.time())
> +    for immobj in conf_objects:
> +        modify_object(immobj, suffix, new_amfnode_dn, new_hostname)
> +
> +    objs = list()
> +    # Limit to what we can scale-in for now.
> +    # Missing objects: comps, comps_cs, swbundles, hchks, csiattrs
Johan: Are they also not scaled out, so the scale in/outs are symmetrical?
> +    allowed_objs = ['SaAmfNode', 'SaClmNode', 'SaAmfSU', 'SaAmfSI', 
> 'SaAmfCSI']
> +    for obj in conf_objects:
> +        if obj.class_name in allowed_objs:
> +            objs.append(obj)
> +
> +    ccb = Ccb(flags=None)
> +
> +    for immobj in objs:
> +        rdn, parent = split_rdn_and_parent(immobj)
> +        rdnindex = get_rdn_attribute_for_class(immobj.class_name)
> +        immobj.attrs[rdnindex][1] = [rdn]
> +        print_object(immobj, immobj.new_dn)
> +        del immobj.attrs['new_dn']
> +        ccb.create(immobj, parent)
> +    ccb.apply()
> +
> +    #sgs_of_sus = []
> +    #for immobj in conf_objects:
> +    #    ccb.create(immobj, parents[immobj])  # Create the new objects
> +    #    if immobj.class_name == 'SaAmfSU':
> +    #        sgs_of_sus.append(immom.get(immobj.dn.split(',', 1)[1]))
> +    #print '[Modifying attributes]'
> +    #for sgr in sgs_of_sus:
> +    #    print 'Set empty value for {0} let AMF handle it'.format(
> +    #        sgr.dn + ': saAmfSGNumPrefInserviceSUs')
> +    #    ccb.modify_value_replace(sgr.dn, 'saAmfSGNumPrefInserviceSUs', 0)
> +    #for ngr in nodegroups_to_join:
> +    #    print "Adding to NodeGroup", ngr.dn
> +    #    ccb.modify_value_add(ngr.dn, 'saAmfNGNodeList', [new_amfnode_dn])
> +    # for itsi in find_sis(sus, NWAYACTIVE): saAmfSIPrefActiveAssignments += 
> 1
> +    #unlock_node(amfnode)
> +    #immom.admin_op_invoke('dn', ADMIN_UNLOCK_IN)
> +    #immom.admin_op_invoke('dn', ADMIN_UNLOCK)
Johan: Remove commented code unless it's needed and then it should be 
used :-)
> +    print '-Scaling out done'
> +
> +
> +def scale_in(hostname):
> +    """ Remove the configuration for the host node """
> +    print '-Scaling in: %s' % hostname
> +    amfnode_dn, clmnode_dn = find_node_dns(hostname)
> +    clmnode = immom.get(clmnode_dn)
> +    amfnode = immom.get(amfnode_dn)
> +
> +    #lock_node(amfnode, clmnode)
> +    subprocess.call(['amf-adm', 'lock', clmnode_dn])
Johan: This should be changed to use an equivalent (?) admin op to AMF 
to avoid the need to call out through a subprocess. Is there a problem 
with using the lock_node function you have defined?
> +    #subprocess.call(['ssh', hostname, '/etc/init.d/opensafd', 'stop'])
Johan: Remove this commented line
> +    sus = find_sus(amfnode_dn)
> +
> +    ccb = Ccb(flags=None)
> +    ccb.delete(amfnode.dn)
> +    ccb.delete(clmnode.dn)
> +    for itsu in sus:
> +        print itsu.dn
> +        ccb.delete(itsu.dn)
> +
> +    sis, apps = find_sis_apps(sus, NORED)
> +    for itsi in sis:
> +        # Check if this SI has a assignment
> +        siass = inst_iter('SaAmfSIAssignment')
> +        assigned = [ass for ass in siass if str.find(ass.dn, itsi.dn) != -1]
> +
> +        # Check if the SU App is used by this SI... Why? Not sure, the 
> example
> +        # I followed did even more stuff here. TODO: Investigate why.
> +        usedapp = [app for app in apps if str.find(itsi.dn, app) != -1]
> +        si_app = itsi.dn.split(',')[1:][0]  # Last split is the SI App
> +        if not assigned and usedapp and si_app in apps:
> +            csis = find_object_type('SaAmfCSI', [itsi])
> +            for csi in csis:
> +                print csi.dn
> +                ccb.delete(csi.dn)
> +            print itsi.dn
> +            ccb.delete(itsi.dn)
> +    ccb.apply()
> +    print '-Scaling in done'
> +
> +if __name__ == '__main__':
> +    parser = argparse.ArgumentParser(
> +        description='Scales OpenSAF by adding and removing node 
> configuration')
> +    parser.add_argument(
> +        '--hostname', type=str, required=True,
> +        help='Hostname for the new node.')
> +    parser.add_argument(
> +        '--copy-from', type=str, required=False, help='Hostname of the '
> +        'existing node to use as template. If not set, the host where the '
> +        'command is executed will be used as template.',
> +        default=os.uname()[1])
> +
> +    args = parser.parse_args()
> +
> +    scale_out(args.hostname, args.copy_from)
> +    scale_in(args.hostname)


------------------------------------------------------------------------------
_______________________________________________
Opensaf-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/opensaf-devel

Reply via email to