On 11/18/2015 02:00 PM, Johan MÃ¥rtensson O wrote:
> 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?
OK
>> +
>> +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?
Sure
>> +
>> +
>> +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.
Done
>> +    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?
OK
>> +
>> +
>> +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)?
Yes
>> +    """ 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?
Yes It could but scaling is not using the lock function right now so it 
is removed.
>> +    """ 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?
Probably yes. But I will do it in another patch
>
>> +    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?
Exactly
>> +    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 :-)
Agree, done
>> +    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?
It should. There is a problem using the lock_node function. Will fix in 
next patch.
>> +    #subprocess.call(['ssh', hostname, '/etc/init.d/opensafd', 'stop'])
> Johan: Remove this commented line
Sure
>> +    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