Hi Rafael, Ack, with the changes below this looks good. It would be good to replace the subprocess call with an admin operation in a later patch.
/ Johan -----Original Message----- From: Rafael Odzakow Sent: den 19 november 2015 15:28 To: Johan Mårtensson O Cc: [email protected] Subject: [PATCH 1 of 1] pyosaf: add sample script to scale opensaf configuration [#1569] python/pyosaf/utils/immom/__init__.py | 11 + python/pyosaf/utils/immom/object.py | 3 + python/samples/scale_opensaf | 323 ++++++++++++++++++++++++++++++++++ 3 files changed, 337 insertions(+), 0 deletions(-) This script will add and remove IMM configuration for a node. Check the help from argparser (-h) for more info. This version is limited and can be viewed as a demo. Scale-in is done after scale-out to clean up any configuration added. diff --git a/python/pyosaf/utils/immom/__init__.py b/python/pyosaf/utils/immom/__init__.py --- a/python/pyosaf/utils/immom/__init__.py +++ b/python/pyosaf/utils/immom/__init__.py @@ -185,3 +185,14 @@ def get_error_strings(ccb_handle): saImmOmCcbGetErrorStrings(ccb_handle, c_strings) return unmarshalNullArray(c_strings) + + +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 = class_description_get(class_name) + for attr_desc in desc: + if attr_desc.attrFlags & saImm.saImm.SA_IMM_ATTR_RDN: + return attr_desc.attrName + return None diff --git a/python/pyosaf/utils/immom/object.py b/python/pyosaf/utils/immom/object.py --- a/python/pyosaf/utils/immom/object.py +++ b/python/pyosaf/utils/immom/object.py @@ -61,6 +61,9 @@ class ImmObject(object): else: raise + self.__dict__["rdn_attribute"] = \ + pyosaf.utils.immom.get_rdn_attribute_for_class(class_name) + def get_value_type(self, attrname): ''' returns IMM value type of the named attribute ''' for attrdef in self.class_desc[self.class_name]: diff --git a/python/samples/scale_opensaf b/python/samples/scale_opensaf new file mode 100755 --- /dev/null +++ b/python/samples/scale_opensaf @@ -0,0 +1,323 @@ +#!/usr/bin/env python +""" Adds and removes IMM configuration for nodes """ + +import re +import hashlib +import time +import argparse +import os +import subprocess +from pyosaf.utils import immom +from pyosaf.utils.immom.ccb import Ccb +from pyosaf.utils.immom.iterator import InstanceIterator as inst_iter +from pyosaf.saAmf import eSaAmfRedundancyModelT, +eSaAmfAdminOperationIdT + +TWO_N = eSaAmfRedundancyModelT.SA_AMF_2N_REDUNDANCY_MODEL +NWAYACTIVE = +eSaAmfRedundancyModelT.SA_AMF_N_WAY_ACTIVE_REDUNDANCY_MODEL +NORED = eSaAmfRedundancyModelT.SA_AMF_NO_REDUNDANCY_MODEL + +ADMIN_UNLOCK = eSaAmfAdminOperationIdT.SA_AMF_ADMIN_UNLOCK +ADMIN_LOCK = eSaAmfAdminOperationIdT.SA_AMF_ADMIN_LOCK +ADMIN_LOCK_IN = eSaAmfAdminOperationIdT.SA_AMF_ADMIN_LOCK_INSTANTIATION +ADMIN_UNLOCK_IN = +eSaAmfAdminOperationIdT.SA_AMF_ADMIN_UNLOCK_INSTANTIATION + + +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 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): + """ Find 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, scalable_redundancy): + """ 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: + if redundancy_of_su(itsu.dn) in scalable_redundancy: + 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_scalable_immobjects(from_amfnode_dn, from_clmnode_dn): + """ Read AMF objects of an allowed type and redundancy from a node. + returns: list of collected ImmObjects + """ + sus = find_sus(from_amfnode_dn, [TWO_N, NWAYACTIVE, NORED]) + 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_immobject(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: + # We have a SU or SI in the DN string. Generate a hash from it. + rename_str = immobj.new_dn[match.start():match.end()] + new_hostname + rename_str += suffix + rename_str = hashlib.md5(rename_str).hexdigest()[:10] + + # Rename the DN and store it in new_dn + 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) + + # Change attributes to match the new node. Admin state is set to something + # accepted by the AMF OI + 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 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_scalable_immobjects(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_immobject(immobj, suffix, new_amfnode_dn, new_hostname) + + objs = list() + # Limit the scale-out objects to what we can scale-in for now. + # objects not scaled out: comps, comps_cs, swbundles, hchks, csiattrs + 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) + immobj.attrs[immobj.rdn_attribute][1] = [rdn] + print_object(immobj, immobj.new_dn) + del immobj.attrs['new_dn'] + ccb.create(immobj, parent) + ccb.apply() + 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) + + subprocess.call(['amf-adm', 'lock', clmnode_dn]) + + sus = find_sus(amfnode_dn, [TWO_N, NWAYACTIVE, NORED]) + + 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) ------------------------------------------------------------------------------ Go from Idea to Many App Stores Faster with Intel(R) XDK Give your users amazing mobile app experiences with Intel(R) XDK. Use one codebase in this all-in-one HTML5 development environment. Design, debug & build mobile apps & 2D/3D high-impact games for multiple OSs. http://pubads.g.doubleclick.net/gampad/clk?id=254741551&iu=/4140 _______________________________________________ Opensaf-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/opensaf-devel
