Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Cisco to Juniper - parser help

by jamescmatt (Novice)
on May 24, 2021 at 10:13 UTC ( #11132953=perlquestion: print w/replies, xml ) Need Help??

jamescmatt has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I only come here as I have been working on a script for a bit and keep running into the same problem. When iterating through the loop, when it hits a service-instance with a filter, it always puts the filter on the following interface. I know I suck at all of this and sorry for the bothering.

Example of configuration file I am using (I call it for instance, gi1_3, and the outgoing file, ge-1/1/1 ( from cli, ./parser.pl gi1_3 ge-1/1/1). What the script does is take an interface configuration, parse through it, and match based on a string. When it matches, it sets it to a variable so I can convert the configuration from Cisco to Juniper. The issue I have as stated above, is that when iterating through, for whatever reason the match on the service-policy is always put on the line after it. Now I know I am not great at perl and I do my best, but I just can't figure out how to fix this, and I have tried everything. EDIT: I found a workable solution(print filter earlier, in hindsight I can probably do this for all matches, and probably will soon), and a few edits. I think this is a great script for anyone in my line of work and can easily be modified for use-case. As such, going to toss in a few updates to the below. I know it isn't perfect and can be made tons better, but I do what I can with what I have.

service instance 20 ethernet description Internal site encapsulation dot1q 17 rewrite ingress tag pop 1 symmetric xconnect 192.168.51.20 838969735 encapsulation mpls mtu 9202 ! service instance 21 ethernet description Client 1 important encapsulation dot1q 18 rewrite ingress tag pop 1 symmetric xconnect 192.168.51.21 838969736 encapsulation mpls mtu 9202 ! service instance 22 ethernet description Client other 2 encapsulation dot1q 22 rewrite ingress tag pop 1 symmetric xconnect 192.168.56.22 838925386 encapsulation mpls mtu 9202 ! service instance 23 ethernet description Client name with fun stuff encapsulation dot1q 3200 second-dot1q 5 rewrite ingress tag pop 2 symmetric service-policy input CoS_222_333_23 xconnect 192.168.56.23 571125465 encapsulation mpls mtu 9202 ! service instance 24 ethernet description Client - not as important encapsulation dot1q 3200 second-dot1q 6 rewrite ingress tag pop 2 symmetric service-policy input 222_444_24 xconnect 192.168.56.24 571637077 encapsulation mpls mtu 9202
Below is the output I get when running my script (only a partial of the output)
set interfaces ge-1/1/1 unit 23 description "Client name with fun stuf +f" set interfaces ge-1/1/1 unit 23 encapsulation vlan-ccc set interfaces ge-1/1/1 unit 23 vlan-tags outer 3200 inner 5 set interfaces ge-1/1/1 unit 23 input-vlan-map pop-pop set interfaces ge-1/1/1 unit 23 output-vlan-map push-push COS: set class-of-service interfaces ge-1/1/1 unit 23 rewrite-rules ieee-80 +2.1 vlan-tag outer-and-inner set protocols l2circuit neighbor 192.168.56.23 interface ge-1/1/1.23 d +escription "Client name with fun stuff" set protocols l2circuit neighbor 192.168.56.23 interface ge-1/1/1.23 c +ontrol-word set protocols l2circuit neighbor 192.168.56.23 interface ge-1/1/1.23 i +gnore-mtu-mismatch set protocols l2circuit neighbor 192.168.56.23 interface ge-1/1/1.23 m +tu 9202 set protocols l2circuit neighbor 192.168.56.23 interface ge-1/1/1.23 p +seudowire-status-tlv set protocols l2circuit neighbor 192.168.56.23 interface ge-1/1/1.23 e +ncapsulation-type ethernet set protocols l2circuit neighbor 192.168.56.23 interface ge-1/1/1.23 v +irtual-circuit-id 571125465 set interfaces ge-1/1/1 unit 24 description "Client - not as important +" set interfaces ge-1/1/1 unit 24 encapsulation vlan-ccc set interfaces ge-1/1/1 unit 24 vlan-tags outer 3200 inner 6 set interfaces ge-1/1/1 unit 24 input-vlan-map pop-pop set interfaces ge-1/1/1 unit 24 output-vlan-map push-push <b>COS: BIZDSLIP_HSIHYP_NOBP_96KBMG</b> This should be on the unit 23. + Instead, the filter for unit 23 is pushed to unit 24, and the filter + for 24 is pushed to 25 etc. set interfaces ge-1/1/1 unit 24 family ccc filter input BIZDSLIP_HSIHY +P_NOBP_96KBMG set class-of-service interfaces ge-1/1/1 unit 24 rewrite-rules ieee-80 +2.1 vlan-tag outer-and-inner
Below is the actual script, please go easy on me. Below is a part of the output I get, notice how unit 23 does not have the COS, and unit 24 has the COS for unit 23.
#!/usr/bin/perl use warnings; use strict; my $interface = $ARGV[1]; my $intcisco = $ARGV[0]; my $intfc = $interface; $interface =~ s/\//\_/g; my $filename = "$intcisco.txt"; my $newfile = "$interface-tmp.txt"; my $line; my $desc = ""; my @splitunit = ""; my $unit = ""; my @splitxc = ""; my $neighbor = ""; my @splitvlan = ""; my $vlanida = ""; my $vlanidb = ""; my $pop; my $daf = 0; open(FH, '<', $filename) or die $!; open(NF, '>', $newfile) or die "$ARGV[1] does not exist.\n"; while (<FH>) { $line = <FH>; print "$line\n"; foreach my $line(<FH>){ my $vcid; my $tag; my @splittag; my $pop; my $xcon; my $cos; my @splitcos; ### Match Service instance, variable for unit ### if ($line =~ /service instance/s){ @splitunit = split(' ',$line); $unit = $splitunit[2]; #print "UNIT: $unit\n"; #debug } ### Match description, format it ### if ($line =~ /description/s){ $desc = $line; $desc =~ s/^\s+//; $desc =~ s/description/description "/g; $desc =~ s/description " /description "/g; $desc =~ s/\r/"/; #print "DESCRIPTION: $desc\n" #debug; } ### Match encapsulation for vlan/s ### if ($line =~ m/encapsulation dot1q/s){ @splitvlan = split(' ', $line); $vlanida = $splitvlan[2]; $vlanidb = $splitvlan[4]; } ### Match service-policy for Filter/CoS if ($line =~ /service-policy input/g){ @splitcos = split(' ', $line); $cos = $splitcos[2]; push(@cosarray,($cos)); #print "COS: $cos\n"; #debug # not perfect, I know, but it works now print NF "set interfaces $intfc unit $unit family ccc filt +er input @cosarray\n"; } ### Match on xconnect to split off neighbor/vcid ### if ($line =~ m/xconnect/s){ $xcon = $line; @splitxc = split(' ', $xcon); $neighbor = $splitxc[1]; $vcid = $splitxc[2]; } ### match on rewrite for tags ### if ($line =~ m/rewrite/s){ my $tag = $line; @splittag = split(' ', $tag); $pop = $splittag[4]; } ### match for bridge ### if ($line =~ /bridge/g) { @splitbridge = split(' ', $line); $bridge = @splitbridge[1]; push(@bridgevlans,($bridge)); print "@bridgevlans\n"; } ### Print to file after converting to Juniper ### if ($unit eq $bridge){ print NF "set interfaces $intfc unit $unit $desc\n"; print NF "set interfaces $intfc unit $unit vlan-id $vlanida\n"; + print NF "set interfaces $intfc unit $unit encapsulation vlan-brid +ge\n"; #print NF "set interfaces $intfc unit $unit input-vlan-map pop\n"; #print NF "set interfaces $intfc unit $unit input-vlan-map push\n\ +n"; #print NF "set interfaces $intfc unit $unit mtu 9202\n"; print NF "set bridge-domains $unit interface $intfc.$unit\n"; print NF " \n"; } elsif ($pop eq "1") { print NF "set interfaces $intfc unit $unit $desc\n"; print NF "set interfaces $intfc unit $unit vlan-id $vlanida\n"; print NF "set interfaces $intfc unit $unit encapsulation vlan-ccc\ +n"; print NF "set interfaces $intfc unit $unit input-vlan-map pop\n"; print NF "set interfaces $intfc unit $unit output-vlan-map push\n\ +n"; #if (!length(@cosarray)) { #print NF "COS: @cosarray\n"; #debug #print NF "set interfaces $intfc unit $unit family ccc filter inpu +t @cosarray\n"; #} } elsif ($pop eq "2"){ print NF "set interfaces $intfc unit $unit $desc\n"; print NF "set interfaces $intfc unit $unit encapsulation vlan-ccc\ +n"; print NF "set interfaces $intfc unit $unit vlan-tags outer $vlanid +a inner $vlanidb\n"; print NF "set interfaces $intfc unit $unit input-vlan-map pop-pop\ +n"; print NF "set interfaces $intfc unit $unit output-vlan-map push-pu +sh\n\n"; #print NF "set class-of-service interfaces $intfc unit $unit class +ifiers ieee-802.1 BMG-COS-Classifer\n"; #print NF "set class-of-service interfaces $intfc unit $unit rewri +te-rules ieee-802.1 BMG-COS-Rewrite\n"; #if (!length(@cosarray)) { #print NF "COS: @cosarray[0]\n"; #debug #print NF "set interfaces $intfc unit $unit family ccc filter inpu +t @cosarray\n"; # } } #print NF "set interfaces $intfc unit $unit family ccc mtu 9202\n" +; if ($neighbor eq ''){} #todo elsif ($vcid eq '') {} #todo else { print NF "set protocols l2circuit neighbor $neighbor interface $in +tfc.$unit $desc\n"; print NF "set protocols l2circuit neighbor $neighbor interface $in +tfc.$unit control-word\n"; print NF "set protocols l2circuit neighbor $neighbor interface $in +tfc.$unit ignore-mtu-mismatch\n"; print NF "set protocols l2circuit neighbor $neighbor interface $in +tfc.$unit mtu 9202\n"; print NF "set protocols l2circuit neighbor $neighbor interface $in +tfc.$unit pseudowire-status-tlv\n"; print NF "set protocols l2circuit neighbor $neighbor interface $in +tfc.$unit encapsulation-type ethernet\n"; print NF "set protocols l2circuit neighbor $neighbor interface $in +tfc.$unit virtual-circuit-id $vcid\n\n"; } if (($pop eq "1") && ($unit !~ /^(51|86|3201|3551|3552|3900|3901|3 +902|3903|3905|3910|3911|3917|3918|3928|3929|3950|3951)$/)){ print NF "set class-of-service interfaces $intfc unit $unit classi +fiers ieee-802.1 CL-OPM-COS\n"; print NF "set class-of-service interfaces $intfc unit $unit rewrit +e-rules ieee-802.1 RR-OPM-COS\n"; print NF "\n"; } if (($pop eq "2") && ($unit !~ /^(51|86|3201|3551|3552|3900|3901|3 +902|3903|3905|3910|3911|3917|3918|3928|3929|3950|3951)$/)){ print NF "set class-of-service interfaces $intfc unit $unit re +write-rules ieee-802.1 vlan-tag outer-and-inner\n"; print NF "\n"; } if ($daf <= 0) { print NF "set protocols oam ethernet link-fault-management interfa +ce $intfc pdu-interval 1000\n"; print NF "set protocols oam ethernet link-fault-management interfa +ce $intfc loopback-tracking\n"; print NF "set protocols oam ethernet link-fault-management interfa +ce $intfc link-discovery active\n"; print NF "set protocols oam ethernet link-fault-management interfa +ce $intfc pdu-threshold 3\n"; print NF "set class-of-service interfaces $intfc scheduler-map POR +TBASED-EGRESS-SCHEDULERMAP-1GE\n"; print NF "set class-of-service interfaces $intfc unit * classifier +s ieee-802.1 COS-Classifier\n"; print NF "set class-of-service interfaces $intfc unit * rewrite-ru +les ieee-802.1 COS-Rewrite\n"; print NF "\n"; $daf++; } } } while(<FH>); close(FH); close(NF);

Replies are listed 'Best First'.
Re: Cisco to Juniper - parser help
by Fletch (Chancellor) on May 24, 2021 at 11:38 UTC

    Couple of high level thoughts (attempted with minimal caffeination, so take with large conglomeration of NaCL and I may be whiffing on template syntax a bit):

    This is almost complex enough you might want to look at using a proper parser (Parse::RecDescent, Marpa::R2) and build some sort of data structure to represent each "interface entry". Alternately maybe at least use "!\n" as your record separator and push the existing parsing logic off into its own sub (which then returns some sort of data structure . . .); doing that might help make sure you're not intermingling blocks and can (slightly more easily) start with a fresh parse state each one.

    Also rather than a bunch of hardcoded print statements for something like this I'd use Template Toolkit (see also Template) and write something which takes the data produced by the parsing bits and then passes those to something which expands into the final output lines.

    [% MACRO interface_metainfo( if_info ) BEGIN %] set interfaces ge-1/1/1 unit 23 description "[% if_info.description %] +" set interfaces ge-1/1/1 unit 23 encapsulation [% if_info.encapsulation + %] set interfaces ge-1/1/1 unit 23 vlan-tags [% FOR tag IN if_info.vlan_t +ags.keys %][% tag %] [% if_info.vlan_tags.$tag %][% END %] ## YADDA YADDA YADDA [% END %] [%- CLEAR -%] [% FOR if IN interface_list %] [% interface_metainfo( if ) %] [% protocol_block( if ) %] [% class_of_service_block( if ) %] [% END %]

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Thanks, I will definitely be looking into doing something like that once I can get the filter to align correctly to the unit.

        Aah another thing just noticed (after caffeine has put the mind in motion a bit) you've got an outer while loop reading your input FH handle as well as a nested foreach reading the same handle. You look to be effectively reading the first line and discarding it (after printing it to STDOUT), then reading the rest of the file. That's . . . probably not what you want and may be why you're out of sync.

        As I mentioned, if you change things to do record reads (see perlvar for $/) and then have a single while reading input records once that may help keep things straight(er).

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: Cisco to Juniper - parser help
by LanX (Sage) on May 24, 2021 at 16:30 UTC
    This should give you a clean start.

    I suppose you'll be able to adjust the methods to your needs.

    Especially ->__out__ where you can write the new format based on the already parsed data.

    Based on the limited insight I have, I tried to keep it as generic as possible.

    YMMV but HTH! :)

    # https://perlmonks.org/index.pl?node_id=11132953 use strict; use warnings; use Data::Dump qw/pp dd/; my $obj = "CiscoService"; while (<DATA>) { chomp; my ($indent,$data) = /^(\s*)(.*)$/; my ($call, @args) = split/\s+/, $data; $call =~ s/^!$/__out__/; $call =~ s/-/_/g; if ( my $meth = $obj->can($call) ) { $obj = $meth->( $obj, length($indent),@args) } else { warn "<$call> not impemented"; } } package CiscoService; use Data::Dump qw/pp dd/; sub service { my ($self,$indent,@args) = @_; return bless {__head__ => \@args, sub=>{} },__PACKAGE__; } sub description { my ($self,$indent,@args) = @_; $self->{sub}{description} = \@args; return $self; } sub service_policy { my ($self,$indent,@args) = @_; $self->{sub}{"service-policy"} = \@args; return $self; } sub encapsulation { my ($self,$indent,@args) = @_; $self->{sub}{encapsulation} = \@args; return $self; } sub rewrite { my ($self,$indent,@args) = @_; $self->{sub}{rewrite} = \@args; return $self; } sub xconnect { my ($self,$indent,@args) = @_; $self->{sub}{xconnect} = {head =>\@args, sub=>{}}; return $self; } sub mtu { my ($self,$indent,@args) = @_; $self->{sub}{xconnect}{sub}{mtu} = \@args; return $self; } sub __out__ { my ($self,$indent,@args) = @_; pp $self; return "CiscoService"; } package main; __DATA__ service instance 20 ethernet description Internal site encapsulation dot1q 17 rewrite ingress tag pop 1 symmetric xconnect 192.168.51.20 838969735 encapsulation mpls mtu 9202 ! service instance 21 ethernet description Client 1 important encapsulation dot1q 18 rewrite ingress tag pop 1 symmetric xconnect 192.168.51.21 838969736 encapsulation mpls mtu 9202 ! service instance 22 ethernet description Client other 2 encapsulation dot1q 22 rewrite ingress tag pop 1 symmetric xconnect 192.168.56.22 838925386 encapsulation mpls mtu 9202 ! service instance 23 ethernet description Client name with fun stuff encapsulation dot1q 3200 second-dot1q 5 rewrite ingress tag pop 2 symmetric service-policy input CoS_222_333_23 xconnect 192.168.56.23 571125465 encapsulation mpls mtu 9202 ! service instance 24 ethernet description Client - not as important encapsulation dot1q 3200 second-dot1q 6 rewrite ingress tag pop 2 symmetric service-policy input 222_444_24 xconnect 192.168.56.24 571637077 encapsulation mpls mtu 9202 !

    bless({ __head__ => ["instance", 20, "ethernet"], sub => { description => ["Internal", "site"], encapsulation => ["dot1q", 17], rewrite => ["ingress", "tag", "pop", 1, "symmetric"], xconnect => { head => ["192.168.51.20", 838969735, "encapsula +tion", "mpls"], sub => { mtu => [9202] }, }, }, }, "CiscoService") bless({ __head__ => ["instance", 21, "ethernet"], sub => { description => ["Client", 1, "important"], encapsulation => ["dot1q", 18], rewrite => ["ingress", "tag", "pop", 1, "symmetric"], xconnect => { head => ["192.168.51.21", 838969736, "encapsula +tion", "mpls"], sub => { mtu => [9202] }, }, }, }, "CiscoService") bless({ __head__ => ["instance", 22, "ethernet"], sub => { description => ["Client", "other", 2], encapsulation => ["dot1q", 22], rewrite => ["ingress", "tag", "pop", 1, "symmetric"], xconnect => { head => ["192.168.56.22", 838925386, "encapsula +tion", "mpls"], sub => { mtu => [9202] }, }, }, }, "CiscoService") bless({ __head__ => ["instance", 23, "ethernet"], sub => { "description" => ["Client", "name", "with", "fun", "stuff"], "encapsulation" => ["dot1q", 3200, "second-dot1q", 5], "rewrite" => ["ingress", "tag", "pop", 2, "symmetric"], "service-policy" => ["input", "CoS_222_333_23"], "xconnect" => { head => ["192.168.56.23", 571125465, "encaps +ulation", "mpls"], sub => { mtu => [9202] }, }, }, }, "CiscoService") bless({ __head__ => ["instance", 24, "ethernet"], sub => { "description" => ["Client", "-", "not", "as", "important"], "encapsulation" => ["dot1q", 3200, "second-dot1q", 6], "rewrite" => ["ingress", "tag", "pop", 2, "symmetric"], "service-policy" => ["input", "222_444_24"], "xconnect" => { head => ["192.168.56.24", 571637077, "encaps +ulation", "mpls"], sub => { mtu => [9202] }, }, }, }, "CiscoService")

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

Re: Cisco to Juniper - parser help
by jwkrahn (Monsignor) on May 25, 2021 at 06:33 UTC

    A major problem with your code are these lines:

    if ($neighbor eq undef){} elsif ($vcid eq undef) {} else {

    undef can NOT be used in a comparison, you have to use the defined function to determine if a variable is defined or not.

    You also don't need the if/elsif/else structure:

    if ( !defined $neighbor && !defined $vcid ) {

    Another problem is a lot of "uninitialized value" messages which can be fixed by initializing all your variables.

    my $vcid; my $tag; my @splittag; my $pop; my $xcon; my $cos; my @splitcos; # change those to: my $vcid = ''; my $tag = ''; my @splittag; my $pop = ''; my $xcon = ''; my $cos = ''; my @splitcos;
    $unit = $splitunit[2]; # change to: $unit = $splitunit[2] || '';
    $vlanida = $splitvlan[2]; $vlanidb = $splitvlan[4]; # change to: $vlanida = $splitvlan[2] || ''; $vlanidb = $splitvlan[4] || '';
    $cos = $splitcos[2]; # change to: $cos = $splitcos[2];
    $neighbor = $splitxc[1]; $vcid = $splitxc[2]; # change to: $neighbor = $splitxc[1] || ''; $vcid = $splitxc[2] || '';
    $pop = $splittag[4]; # change to: $pop = $splittag[4] || '';
    if ($neighbor eq undef){} elsif ($vcid eq undef) {} else { # change to: if ( length $neighbor && length $vcid ) {

    Try running your code with those changes and post again with your feedback.

      A large block of global vars set to some arbitrary initial value is a code smell, nay, a code stink. With a cursory glance at the code it seems likely that the warnings are really and truly saying something important about the logic of the code. I would remove all the global variables then put them back in the smallest scope that makes sense, ideally initializing the variables where they are declared with an appropriate value rather than just any old value to shut up the warnings.

      If the code logic dictates that the variables are populated piecemeal during multiple passes through the loop I'd either add code to handle unexpectedly unpopulated values or fix the logic errors as appropriate. In either case just shutting up the warnings is stinky coding.

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Cisco to Juniper - parser help
by hippo (Bishop) on May 24, 2021 at 15:57 UTC

    I have downloaded your code and your supplied input. I have run the code against the input with the command line ./11132953.pl gi1_3 ge-1/1/1 and I have to report that BIZDSLIP_HSIHYP_NOBP_96KBMG appears nowhere in the output at all. But that isn't surprising because it doesn't appear in the input either.

    Can you explain where you think this string should come from? Thanks.

    BTW, when running your code the screen just fills with Use of uninitialized value ... warnings which makes spotting a real problem so much more difficult. It would help your cause to try to remove these.


    🦛

Re: Cisco to Juniper - parser help
by bliako (Monsignor) on May 24, 2021 at 15:56 UTC

    I wonder if there is already in CPAN a module to parse these configuration files, what are they called? You can search here: https://metacpan.org

Re: Cisco to Juniper - parser help
by LanX (Sage) on May 24, 2021 at 13:09 UTC
    Does this mtu info belong to the previous line or is it independent, i.e. does indentation matter?

    xconnect 192.168.51.21 838969736 encapsulation mpls mtu 9202
    edit:

    do you need to have read all services before you can start the output, or can you write directly for each service ?

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      the MTU is a part of the above line, and is the last line for each service instance. But in this case, I am not using it at all, since my default MTU on the Juniper is already configured. It doesnt matter how it reads/writes, as long as the output is coherent ( I am outputting it this way because I can generally keep the new interface configuration/l2circuits in order).
        > It doesnt matter how it reads/writes, as long as the output is coherent

        Sorry I don't know what that means.

        Can you output each service independently or do you need to know all the data before writing?

        Update

        My approach would be using a flip-flop operator to read each service and to output at the end.

        Each line would be processed by a dedicated subroutine based on the first word.

        Like service::description() and so on.

        This would make your code far more readable and easier to maintain.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

        ) I can help you with Perl, but not with Cisco or Juniper, sorry.

        > the MTU is a part of the above line,

        does it mean

        • it's a continuation of the previous line indicated by the extra indentation
        • it's nested information, and this cisco format has to be parsed like a tree
        ?

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11132953]
Approved by haukex
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (3)
As of 2021-11-28 03:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?