Changeset b0581ac


Ignore:
Timestamp:
May 14, 2010 10:33:32 AM (15 years ago)
Author:
Ted Faber <faber@…>
Branches:
axis_example, compt_changes, info-ops, master, version-3.01, version-3.02
Children:
da5b93c
Parents:
afc4af4
Message:

pretty serviceable now

File:
1 edited

Legend:

Unmodified
Added
Removed
  • fedd/compose.py

    rafc4af4 rb0581ac  
    1010
    1111class constraint:
    12     def __init__(self, required=False, accepts=None, provides=None):
     12    def __init__(self, name=None, required=False, accepts=None, provides=None,
     13            match=None):
     14        self.name = name
    1315        self.required = required
    1416        self.accepts = accepts or []
    1517        self.provides = provides or []
     18        self.match = match
    1619
    1720    def __str__(self):
    18         return "%s:%s:%s" % (self.required, ",".join(self.provides),
    19                 ",".join(self.accepts))
    20 
    21 
    22 def add_to_map(exp, mark):
    23     valid_marks = ('stub', 'transit')
    24 
     21        return "%s:%s:%s:%s" % (self.name, self.required,
     22                ",".join(self.provides), ",".join(self.accepts))
     23
     24
     25def add_to_map(exp, mark, provides, accepts):
     26    """
     27    Create a constraint from a string of the form
     28        name:required:provides:accepts
     29    where name is the name of the node in the current topology, if the second
     30    field is "required" this is a required constraint, a list of attributes
     31    that this connection point provides and a list that it accepts.  The
     32    provides and accepts lists are comma-separated.  Note that the string
     33    passed in as exp should have whitespace removed. The constraint is added to
     34    the mark dict under the name key and entries in the provides and accepts
     35    are made to teh constraing for each attribute that it provides or accepts.
     36    """
    2537    nn, r, p, a = exp.split(":")
    2638    p = p.split(",")
    2739    a = a.split(",")
    28     mark[nn] = constraint(r == 'required', p, a)
    29 
    30 def annotate_topo(top, mark):
    31     def make_new_name(names):
    32         i = 0
    33         n = "mark%d" % i
    34         while n in names:
    35             i += 1
    36             n = "mark%d" % i
    37         names.add(n)
    38         return n
    39 
    40     snames = set([ s.name for s in top.substrates ])
    41 
     40    c = constraint(name=nn, required=(r == 'required'), provides=p, accepts=a)
     41    mark[nn] = c
     42
     43    for attr, dict in ((p, provides), (a, accepts)):
     44        for a in attr:
     45            if a not in dict: dict[a] = [c]
     46            else: dict[a].append(c)
     47
     48def make_new_name(names, prefix="name"):
     49    """
     50    Generate an identifier not present in names by appending an integer to
     51    prefix.  The new name is inserted into names and returned.
     52    """
     53    i = 0
     54    n = "%s%d" % (prefix,i)
     55    while n in names:
     56        i += 1
     57        n = "%s%d" % (prefix,i)
     58    names.add(n)
     59    return n
     60
     61def add_interfaces(top, mark):
     62    """
     63    Add unconnected interfaces to the nodes whose names are keys in the mark
     64    dict.  As the interface is added change the name field of the contstraint
     65    in mark into a tuple containing the node name, interface name, and topology
     66    in which they reside.
     67    """
    4268    for e in top.elements:
    43         for n in e.name:
    44             if n in mark:
    45                 if all([ not i.get_attribute('export') for i in e.interface]):
    46                     con = mark[n]
    47                     inames = set([x.name for x in e.interface])
    48                     sn = make_new_name(snames)
    49                     ii = make_new_name(inames)
    50                     s = topdl.Substrate(name=sn)
    51                     i = topdl.Interface(substrate=sn, name=ii, element=e,
    52                             attribute=[
    53                                 topdl.Attribute(attribute='composition_point',
    54                                     value='True'),
    55                                 topdl.Attribute(attribute='required',
    56                                     value = "%s" % con.required),
    57                                 topdl.Attribute(attribute='provides',
    58                                     value=",".join(con.provides)),
    59                                 topdl.Attribute(attribute='accepts',
    60                                     value=",".join(con.accepts)),
    61                                 ]
    62                             )
    63                     e.interface.append(i)
    64                     top.substrates.append(s)
    65 
    66     top.incorporate_elements()
     69        if e.name in mark:
     70            con = mark[e.name]
     71            ii = make_new_name(set([x.name for x in e.interface]), "inf")
     72            e.interface.append(
     73                    topdl.Interface(substrate=[], name=ii, element=e))
     74            con.name = (e.name, ii, top)
     75
     76def localize_names(top, names, marks):
     77    """
     78    Take a topology and rename any substrates or elements that share a name
     79    with an existing computer or substrate.  Keep the original name as a
     80    localized_name attribute.  In addition, remap any constraints or interfaces
     81    that point to the old name over to the new one.  Constraints are found in
     82    the marks dict, indexed by node name.  Those constraints name attributes
     83    have already been converted to triples (node name, interface name,
     84    topology) so only the node name needs to be changed.
     85    """
     86    sub_map = { }
     87    for s in top.substrates:
     88        s.set_attribute('localized_name', s.name)
     89        if s.name in names:
     90            sub_map[s.name] = n = make_new_name(names, "substrate")
     91            s.name = n
     92        else:
     93            names.add(s.name)
     94
     95    for e in [ e for e in top.elements if isinstance(e, topdl.Computer)]:
     96        e.set_attribute('localized_name', e.name)
     97        if e.name in names:
     98            nn= make_new_name(names, "computer")
     99            if e.name in marks:
     100                n, i, t = marks[e.name].name
     101                marks[e.name].name = (nn, i, t)
     102            e.name = nn
     103        else:
     104            names.add(e.name)
     105
     106        # Interface mapping.  The list comprehension creates a list of
     107        # substrate names where each element in the list is replaced by the
     108        # entry in sub_map indexed by it if present and left alone otherwise.
     109        for i in e.interface:
     110            i.substrate = [ sub_map.get(ii, ii) for ii in i.substrate ]
     111
     112def meet_constraints(candidates, provides, accepts):
     113    """
     114    Try to meet all the constraints in candidates using the information in the
     115    provides and accepts dicts (which index constraints that provide or accept
     116    the given attribute). A constraint is met if it can be matched with another
     117    constraint that provides an attribute that the first constraint accepts.
     118    Only one match per pair is allowed, and we always prefer matching a
     119    required constraint to an unreqired one.  If all the candidates can be
     120    matches, return True and return False otherwise.
     121    """
     122    got_all = True
     123    for c in candidates:
     124        if not c.match:
     125            match = None
     126            for a in c.accepts:
     127                for can in provides.get(a,[]):
     128                    # A constraint cannot satisfy itself, nor do we allow loops
     129                    # within the same topology.  This also excludes matched
     130                    # constraints.
     131                    if can.name != c.name and can.name[2] != c.name[2] and \
     132                            not can.match:
     133                        match = can
     134                        # Within providers, prefer matches against required
     135                        # composition points
     136                        if can.required:
     137                            break
     138                # Within acceptance categories, prefer matches against required
     139                # composition points
     140                if match and match.required:
     141                    break
     142
     143            # Done checking all possible matches.
     144            if match:
     145                match.match = c
     146                c.match = match
     147            else:
     148                got_all = False
     149    return got_all
    67150
    68151def remote_ns2topdl(uri, desc, cert):
     152    """
     153    Call a remote service to convert the ns2 to topdl (and in fact all the way
     154    to a topdl.Topology.
     155    """
    69156
    70157    req = { 'description' : { 'ns2description': desc }, }
     
    85172        return None
    86173
     174def connect_composition_points(top, contraints):
     175    """
     176    top is a topology containing copies of all the topologies represented in
     177    the contsraints, flattened into one name space.  This routine inserts the
     178    additional substrates required to interconnect the topology as described by
     179    the constraints.  After the links are made, unused connection points are
     180    purged.
     181    """
     182    done = set()
     183    for c in constraints:
     184        if c not in done and c.match:
     185            # c is an unprocessed matched constraint.  Create a substrate
     186            # and attach it to the interfaces named by c and c.match.
     187            sn = make_new_name(names, "sub")
     188            s = topdl.Substrate(name=sn)
     189            connected = 0
     190            # Walk through all the computers in the topology
     191            for e in [ e for e in top.elements \
     192                    if isinstance(e, topdl.Computer)]:
     193                # if the computer matches the computer and interface name in
     194                # either c or c.match, connect it.  Once both computers have
     195                # been found and connected, exit the loop walking through all
     196                # computers.
     197                for comp, inf in (c.name[0:2], c.match.name[0:2]):
     198                    if e.name == comp:
     199                        for i in e.interface:
     200                            if i.name == inf:
     201                                i.substrate.append(sn)
     202                                connected += 1
     203                                break
     204                        break
     205                # Connected both, so add the substrate to the topology
     206                if connected == 2:
     207                    top.substrates.append(s)
     208                    break
     209            # c and c.match have been processed, so add them to the done set
     210            done.add(c)
     211            done.add(c.match)
     212
     213    # All interfaces with matched constraints have been connected.  Cull any
     214    # interfaces unassigned to a substrate
     215    for e in [ e for e in top.elements if isinstance(e, topdl.Computer)]:
     216        if any([ not i.substrate for i in e.interface]):
     217            e.interface = [ i for i in e.interface if i.substrate ]
     218
     219    top.incorporate_elements()
     220
     221
     222
    87223const_re = re.compile("\s*#\s*COMPOSITION:\s*([^:]+:[^:]+:.*)")
    88224
     
    94230
    95231opts, args = parser.parse_args()
     232
    96233
    97234if opts.cert:
     
    102239    cert = None
    103240
     241comps = [ ]
     242names = set()
     243constraints = [ ]
     244provides = { }
     245accepts = { }
    104246for fn in args:
    105247    marks = { }
     
    111253            m = const_re.search(l)
    112254            if m:
    113                 add_to_map(re.sub('\s', '', m.group(1)), marks)
     255                add_to_map(re.sub('\s', '', m.group(1)), marks, provides,
     256                        accepts)
    114257    except EnvironmentError, e:
    115         print >>sys.stderr, "Error on %S: %s" % (fn, e)
    116 
    117     print marks
     258        print >>sys.stderr, "Error on %s: %s" % (fn, e)
     259        continue
    118260
    119261    top = remote_ns2topdl(opts.url, contents, cert)
    120 
    121     if top:
    122         annotate_topo(top, marks)
    123         print topdl.topology_to_xml(top)
    124     else:
    125         sys.exit("Topology conversion failed on %s" % fn)
     262    add_interfaces(top, marks)
     263    localize_names(top, names, marks)
     264    top.incorporate_elements()
     265    constraints.extend(marks.values())
     266    comps.append(top)
     267
     268# Now the various components live in the same namespace and are marked with
     269# their composition requirements.
     270
     271if not meet_constraints([c for c in constraints if c.required],
     272        provides, accepts):
     273    sys.exit("Could not meet all required constraints")
     274meet_constraints([ c for c in constraints if not c.match ], provides, accepts)
     275
     276# Make a topology containing all elements and substrates from components that
     277# had matches.
     278comp = topdl.Topology()
     279for t in set([ c.name[2] for c in constraints if c.match]):
     280    comp.elements.extend([e.clone() for e in t.elements])
     281    comp.substrates.extend([s.clone() for s in t.substrates])
     282
     283# Add substrates and connections corresponding to composition points
     284connect_composition_points(comp, constraints)
     285
     286print topdl.topology_to_xml(comp, top='experiment')
Note: See TracChangeset for help on using the changeset viewer.