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