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