<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on Welcome to the World of Tomorrow</title>
        <link>https://juliopdx.com/posts/</link>
        <description>Recent content in Posts on Welcome to the World of Tomorrow</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright>
        <lastBuildDate>Mon, 02 Dec 2024 14:53:36 -0800</lastBuildDate>
        <atom:link href="https://juliopdx.com/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>NATS and Configuration Minions</title>
            <link>https://juliopdx.com/2024/12/02/nats-and-configuration-minions/</link>
            <pubDate>Mon, 02 Dec 2024 14:53:36 -0800</pubDate>
            
            <guid>https://juliopdx.com/2024/12/02/nats-and-configuration-minions/</guid>
            <description>Introduction Two blogs in one year. Who do I think I am? In all seriousness, thank you for all the feedback on the previous blog. As always, it really means a lot. This one might go a bit sideways, but building something like this has always been on my mind, and I got another spark to get it done while attending AutoCon2.
I caught the first half of a talk by Mircea Ulinic from Digitial Ocean.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Two blogs in one year. Who do I think I am? In all seriousness, thank you for all the feedback on the previous blog. As always, it really means a lot. This one might go a bit sideways, but building something like this has always been on my mind, and I got another spark to get it done while attending AutoCon2.</p>
<p>I caught the first half of a talk by Mircea Ulinic from Digitial Ocean. In that talk, Mircea went over how the company leverages automation at an incredible scale. They eventually covered a bit on <a href="https://github.com/saltstack/salt">Salt</a> and how they leverage proxy minions to effectively have a one-to-one mapping to a networking node. Imagine one proxy being deployed for each device; the proxy would handle the job when a task needs to be performed on said device.</p>
<p>I wanted to try making something similar, but with the huge caveat I would only tackle one portion—configuration management. I know everyone talks about configuration management, but stick with me and let me have a little fun in my small world. This is a breakdown of how I got a minimal viable product (MVP) running.</p>
<h2 id="what-is-nats">What is NATS</h2>
<p>I&rsquo;m not going to bore anyone with a drawn-out definition, but at its core, <a href="https://nats.io/">NATS</a> is a messaging system that ensures your message will arrive at most once at a subscribed destination. NATS does this in the way of subjects. We might publish a message to <code>say.hello</code> and someone else will be subscribed to listen to messages on <code>say.hello</code>. The subscriber would then get the message and whatever content is included in the message. The NATS server handles the passing of messages. There you go, NATS in a nutshell or paragraph. Side note, the <a href="https://docs.nats.io/">NATS documentation</a> is excellent. Please go check it out.</p>
<p><img src="/blog/images/ncm/nats.svg" alt="Example of a NATS workflow"></p>
<h2 id="the-workflow">The Workflow</h2>
<p>That sounds cool, but again, this blog is focused mainly on Network Engineering, so why do we care? For this example, we will leverage NATS as our messaging system to communicate with our minions or runners or whatever other name you&rsquo;d like to give them. We&rsquo;ll do this through subjects like you saw earlier, but our subjects will be named after nodes and the action we would like performed.</p>
<p><img src="/blog/images/ncm/goal.svg" alt="Example of a NATS workflow with network devices"></p>
<h2 id="the-topology">The Topology</h2>
<p>The topology below looks a bit wild at first glance, but honestly, it&rsquo;s not as wild as some network diagrams I&rsquo;ve seen in the past 😅. I&rsquo;ll go over each portion piece by piece.</p>
<p><img src="/blog/images/ncm/topo.svg" alt="The overall topology"></p>
<h3 id="the-builder-and-the-watch-service">The Builder and the Watch Service</h3>
<p>Everything in this lab is running on my 2019 Lenovo X1 Carbon laptop. You should be able to run this as well 😅. I think phones are more powerful at this point. The builder (my computer) is running <a href="https://avd.arista.com/stable/index.html">AVD</a>; this isn&rsquo;t required as we&rsquo;re only using AVD to generate our final device configurations. You can use whatever methodology or framework you prefer. AVD will generate device configurations in the <code>deployment/intended/configs</code> directory. Each file will be called something like <code>leaf1.cfg</code>.</p>
<p><img src="/blog/images/ncm/builder.svg" alt="The builder topology"></p>
<p>The watch service is fairly short-winded for this example. We leverage the <a href="https://github.com/samuelcolvin/watchfiles">watchfiles</a> Python package and point it to a directory to look for file changes. Once we see a change, we massage some data to get our device name and read the contents. When this is done, we leverage the <a href="https://github.com/nats-io/nats.py">NATS</a> Python package to publish a message to the specific subject.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> asyncio
<span style="color:#f92672">import</span> os

<span style="color:#f92672">import</span> aiofiles
<span style="color:#f92672">import</span> nats
<span style="color:#f92672">from</span> watchfiles <span style="color:#f92672">import</span> awatch


<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">watch_directory</span>(nc, path):
    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">for</span> changes <span style="color:#f92672">in</span> awatch(path):
        <span style="color:#66d9ef">for</span> _, file_path <span style="color:#f92672">in</span> changes:
            <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">with</span> aiofiles<span style="color:#f92672">.</span>open(file_path, mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> f:
                contents <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> f<span style="color:#f92672">.</span>read()
                device_name <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>splitext(os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>basename(file_path))
                <span style="color:#66d9ef">await</span> nc<span style="color:#f92672">.</span>publish(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;dc1.</span><span style="color:#e6db74">{</span>device_name[<span style="color:#ae81ff">0</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">.configure&#34;</span>, contents<span style="color:#f92672">.</span>encode())


<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    nc <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> nats<span style="color:#f92672">.</span>connect(<span style="color:#e6db74">&#34;nats://localhost:4222&#34;</span>)
    <span style="color:#66d9ef">await</span> watch_directory(nc, <span style="color:#e6db74">&#34;deployment/intended/configs&#34;</span>)
    <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>Event()<span style="color:#f92672">.</span>wait()


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    asyncio<span style="color:#f92672">.</span>run(main())

</code></pre></div><p><img src="/blog/images/ncm/watch.svg" alt="The Watch service"></p>
<h3 id="the-nats-server">The NATS Server</h3>
<p>The NATS Server will handle all the communication between the publishers and subscribers. There&rsquo;s a lot we could do with the configuration, but again, I kept it as simple as possible for MVP. The code block below is all that is required to get a NATS server running with Containerlab. If you have <a href="https://docs.nats.io/running-a-nats-service/introduction/running">NATS Server</a> installed, all you need to get a server running is the following command: <code>nats-server</code> 😊.</p>
<p><img src="/blog/images/ncm/nats-server.svg" alt="The NATS Server"></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">topology</span>:
  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">nats</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nats:latest</span>
      <span style="color:#f92672">ports</span>:
        - <span style="color:#ae81ff">4222</span>:<span style="color:#ae81ff">4222</span>
</code></pre></div><h3 id="the-nats-runners-or-minions">The NATS Runners or Minions</h3>
<p>The NATS runners are where we get more complex. The runners are more containers. Just like the Server is running as a container, so will the runners. The runners themselves contain a simple Python application that is subscribed to specific subjects. The block below is our Dockerfile definition for building the runners.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-Docker" data-lang="Docker"><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> python:3.12-alpine</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span><span style="color:#e6db74"> /app</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> requirements.txt .<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> pip install --no-cache-dir -r requirements.txt<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> config-replace.py .<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;python&#34;</span>, <span style="color:#e6db74">&#34;config-replace.py&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span></code></pre></div><p>Again, trying to keep things as contained as possible. We start with a reasonably small Python base image. We then copy the requirements for our application. Once copied, we immediately install the requirements.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">aiofiles
napalm
nats-py
watchfiles

</code></pre></div><p>We then copy over our <code>config-replace.py</code> file. This file holds the subscriber code and configuration replacement task for our runners. We also specify the command to run once the container is started.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> asyncio
<span style="color:#f92672">import</span> os

<span style="color:#f92672">import</span> nats
<span style="color:#f92672">from</span> napalm <span style="color:#f92672">import</span> get_network_driver

<span style="color:#75715e"># Load DEVICE environment variable to build device construct</span>
DEVICE <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ[<span style="color:#e6db74">&#34;DEVICE&#34;</span>]

device <span style="color:#f92672">=</span> {
    <span style="color:#e6db74">&#34;hostname&#34;</span>: DEVICE,
    <span style="color:#e6db74">&#34;username&#34;</span>: <span style="color:#e6db74">&#34;admin&#34;</span>,
    <span style="color:#e6db74">&#34;password&#34;</span>: <span style="color:#e6db74">&#34;admin&#34;</span>,
}


<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">send_config_replace</span>(data):
    driver <span style="color:#f92672">=</span> get_network_driver(<span style="color:#e6db74">&#34;eos&#34;</span>)
    conn <span style="color:#f92672">=</span> driver(<span style="color:#f92672">**</span>device)
    <span style="color:#66d9ef">try</span>:
        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Connecting to </span><span style="color:#e6db74">{</span>device[<span style="color:#e6db74">&#39;hostname&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">...&#34;</span>)
        conn<span style="color:#f92672">.</span>open()  <span style="color:#75715e"># Open the connection to the device</span>
        print(<span style="color:#e6db74">&#34;Connection established.&#34;</span>)

        <span style="color:#75715e"># Load and preview the replacement configuration</span>
        print(<span style="color:#e6db74">&#34;Loading configuration...&#34;</span>)
        conn<span style="color:#f92672">.</span>load_replace_candidate(config<span style="color:#f92672">=</span>data)
        print(<span style="color:#e6db74">&#34;Configuration changes preview:&#34;</span>)
        print(conn<span style="color:#f92672">.</span>compare_config())

        <span style="color:#75715e"># Commit the changes</span>
        <span style="color:#66d9ef">if</span> conn<span style="color:#f92672">.</span>compare_config():
            print(<span style="color:#e6db74">&#34;Applying configuration...&#34;</span>)
            conn<span style="color:#f92672">.</span>commit_config()
            print(<span style="color:#e6db74">&#34;Configuration replaced successfully.&#34;</span>)
        <span style="color:#66d9ef">else</span>:
            print(<span style="color:#e6db74">&#34;No changes detected. Skipping commit.&#34;</span>)

    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">Exception</span> <span style="color:#66d9ef">as</span> e:
        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;An error occurred: </span><span style="color:#e6db74">{</span>e<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#75715e"># Rollback in case of any failure</span>
        print(<span style="color:#e6db74">&#34;Rolling back configuration...&#34;</span>)
        conn<span style="color:#f92672">.</span>rollback()
    <span style="color:#66d9ef">finally</span>:
        <span style="color:#75715e"># Close the connection</span>
        conn<span style="color:#f92672">.</span>close()
        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Disconnected from </span><span style="color:#e6db74">{</span>device[<span style="color:#e6db74">&#39;hostname&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">.&#34;</span>)


<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    nc <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> nats<span style="color:#f92672">.</span>connect(<span style="color:#e6db74">&#34;nats://nats:4222&#34;</span>)

    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">message_handler</span>(msg):
        subject <span style="color:#f92672">=</span> msg<span style="color:#f92672">.</span>subject
        data <span style="color:#f92672">=</span> msg<span style="color:#f92672">.</span>data<span style="color:#f92672">.</span>decode()
        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Received message on subject &#39;</span><span style="color:#e6db74">{</span>subject<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;:</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">{</span>data<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#66d9ef">await</span> send_config_replace(data)

    <span style="color:#66d9ef">await</span> nc<span style="color:#f92672">.</span>subscribe(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;dc1.</span><span style="color:#e6db74">{</span>DEVICE<span style="color:#e6db74">}</span><span style="color:#e6db74">.configure&#34;</span>, cb<span style="color:#f92672">=</span>message_handler)
    <span style="color:#75715e"># Wait indefinitely until the program is interrupted</span>
    <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>Event()<span style="color:#f92672">.</span>wait()


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    asyncio<span style="color:#f92672">.</span>run(main())

</code></pre></div><p>That&rsquo;s neat, but a lot of it is just some <a href="https://github.com/napalm-automation/napalm">NAPALM</a> code to perform a configuration replacement on our devices. There are two interesting points to mention. When we start these containers, we will pass along an environment variable to tell the runner which device it&rsquo;s effectively assigned to. We use that to build the device dictionary.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">DEVICE <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ[<span style="color:#e6db74">&#34;DEVICE&#34;</span>]

device <span style="color:#f92672">=</span> {
    <span style="color:#e6db74">&#34;hostname&#34;</span>: DEVICE,
    <span style="color:#e6db74">&#34;username&#34;</span>: <span style="color:#e6db74">&#34;admin&#34;</span>,
    <span style="color:#e6db74">&#34;password&#34;</span>: <span style="color:#e6db74">&#34;admin&#34;</span>,
}
</code></pre></div><p>Then, in our main function, we initially connect to our NATS Server on <code>nats:4222</code>. We also build a message handler to instruct the application to do something once a message is received on a particular subject. In our case, we will call the <code>send_config_replace</code> function while also passing along the configuration data.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    nc <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> nats<span style="color:#f92672">.</span>connect(<span style="color:#e6db74">&#34;nats://nats:4222&#34;</span>)

    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">message_handler</span>(msg):
        subject <span style="color:#f92672">=</span> msg<span style="color:#f92672">.</span>subject
        data <span style="color:#f92672">=</span> msg<span style="color:#f92672">.</span>data<span style="color:#f92672">.</span>decode()
        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Received message on subject &#39;</span><span style="color:#e6db74">{</span>subject<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;:</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">{</span>data<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#66d9ef">await</span> send_config_replace(data)

    <span style="color:#66d9ef">await</span> nc<span style="color:#f92672">.</span>subscribe(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;dc1.</span><span style="color:#e6db74">{</span>DEVICE<span style="color:#e6db74">}</span><span style="color:#e6db74">.configure&#34;</span>, cb<span style="color:#f92672">=</span>message_handler)
    <span style="color:#75715e"># Wait indefinitely until the program is interrupted</span>
    <span style="color:#66d9ef">await</span> asyncio<span style="color:#f92672">.</span>Event()<span style="color:#f92672">.</span>wait()
</code></pre></div><p>Once we have all of these pieces defined, we can build the local container image by running the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker build -t nats-config:latest .
</code></pre></div><p>The code block below is how we define the runners in our topology file and pass along their respective node assignments.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">topology</span>:
  <span style="color:#f92672">nodes</span>:
...
    <span style="color:#f92672">runner1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nats-config:latest</span>
      <span style="color:#f92672">env</span>:
        <span style="color:#f92672">DEVICE</span>: <span style="color:#ae81ff">spine1</span>
    <span style="color:#f92672">runner2</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nats-config:latest</span>
      <span style="color:#f92672">env</span>:
        <span style="color:#f92672">DEVICE</span>: <span style="color:#ae81ff">leaf1</span>
    <span style="color:#f92672">runner3</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nats-config:latest</span>
      <span style="color:#f92672">env</span>:
        <span style="color:#f92672">DEVICE</span>: <span style="color:#ae81ff">leaf2</span>
</code></pre></div><p><img src="/blog/images/ncm/minions.svg" alt="The runners"></p>
<h2 id="watch-it-live">Watch it Live</h2>
<p>One thing I did not capture in the clips, you need to be running the watch application while testing this out. You can do so by running <code>python watch.py</code> on the builder. The clips below are woefully small, and I apologize in advance. The first clip shows a few things from left to right:</p>
<ul>
<li>AVD data models have been updated, and we are rebuilding the configuration file artifacts</li>
<li>Localhost is running <code>nats sub &quot;dc1.*.configure&quot;</code> so we can view the messages (giant wall of configuration)</li>
<li>Viewing <code>watch show ip name-server</code> from leaf1</li>
<li>Viewing <code>watch show ip name-server</code> from leaf2</li>
<li>Viewing <code>watch show ip name-server</code> from spine1</li>
</ul>
<p><img src="https://media.giphy.com/media/Z2rv4mcOHdZn59XopY/giphy.gif" alt="Application in action"></p>
<p>In the second clip, we get a view from within one of the runners—in this case, the runner assigned to leaf1. Once we run a new build, we can see the print statements from the config-replace script.</p>
<p><img src="https://media.giphy.com/media/1XMByX7XL5QKzPJ03n/giphy.gif" alt="Application in action"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>With the correct control mechanisms, I could see something like this leveraged in areas where a lot of change occurs frequently. Another area where I could see this is if you are starting to run into the scale limitations of running entire jobs from one server or control node. NATS deployments can include multiple servers in a dynamic mesh. I could see an army of minions assigned to the cluster, all listening for configuration jobs.</p>
<p>Another area where this could be interesting is sending messages to minions to perform things like health checks or validations—something for down the road. Another point I want to emphasize is that the code you see here is nowhere near production level. The config replace portion could use a lot of love and someone who really knows how to write async stuff. Thank you all for reading this far. I hope you enjoyed it and got something out of it.</p>
<ul>
<li><a href="https://unsplash.com/photos/a-group-of-minion-figurines-sitting-on-top-of-a-counter-uoII4yXTCao">Featured Image by Jiayuan Lian</a></li>
<li><a href="https://github.com/JulioPDX/nats-config-minions">Github Repository</a></li>
<li><a href="https://docs.nats.io/">NATS</a></li>
<li><a href="https://avd.arista.com/">AVD</a></li>
<li><a href="https://containerlab.dev/">Containerlab</a></li>
<li><a href="https://github.com/napalm-automation/napalm">NAPALM</a></li>
<li><a href="https://github.com/samuelcolvin/watchfiles">watchfiles</a></li>
<li><a href="https://github.com/Tinche/aiofiles">aiofiles</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Codespaces for Network Engineers and Educators</title>
            <link>https://juliopdx.com/2024/11/25/codespaces-for-network-engineers-and-educators/</link>
            <pubDate>Mon, 25 Nov 2024 15:08:58 -0700</pubDate>
            
            <guid>https://juliopdx.com/2024/11/25/codespaces-for-network-engineers-and-educators/</guid>
            <description>Introduction Can you believe it&amp;rsquo;s been almost a year since my last blog? It&amp;rsquo;s incredible how fast time flies. Thank you all for continuing to read the blog and sharing it with engineers in the community. I can&amp;rsquo;t thank you enough for all the support and encouragement I&amp;rsquo;ve received on this journey.
This blog is going to take a bit of a turn. I usually write in the form of a tool I have used or been presented with and think, &amp;ldquo;This is incredible; more people should know about it.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Can you believe it&rsquo;s been almost a year since my last blog? It&rsquo;s incredible how fast time flies. Thank you all for continuing to read the blog and sharing it with engineers in the community. I can&rsquo;t thank you enough for all the support and encouragement I&rsquo;ve received on this journey.</p>
<p>This blog is going to take a bit of a turn. I usually write in the form of a tool I have used or been presented with and think, &ldquo;This is incredible; more people should know about it.&rdquo; My other perspective is, &ldquo;Can I explain this thing in a somewhat more consumable way.&rdquo; This stems from the idea that to really challenge your understanding, teach.</p>
<p>Teaching leads us to the idea behind this blog. I was recently at AutoCon 2 by <a href="https://networkautomation.forum/">Network Automation Forum</a> (great event) and I met an interesting individual from New Mexico. I won&rsquo;t say their name publicly, but let&rsquo;s call them Bob for this blog. Bob is an instructor at a college and teaches future engineers about all things networking. Their students go through Introductory courses (think CCNA level). However, Bob has an interesting challenge: they want their students to continue learning advanced networking concepts. Bob has a tight budget (if any) and would like to avoid managing a fleet of resources to manage students' lab environments.</p>
<p>You all know Containerlab was at the forefront of my mind. Containerlab and a concept a colleague of mine has been working on to build reproducible labs with GitHub Codespaces got my brain spinning on a possible solution for Bob and their students.</p>
<h2 id="the-idea-behind-codespaces">The Idea Behind Codespaces</h2>
<p>Some of you may be wondering, &ldquo;What a Codespace?&rdquo; Think of a Codespace as an environment running on GitHub&rsquo;s infrastructure. Another way to look at it is like a portable development environment that can be accessed by a web browser. In its most basic form, all you need to access a Codespace is a GitHub account (free), computer, and internet access. Please see the <a href="https://github.com/features/codespaces">official documentation</a> for more information. The image below was inspired by the official documentation. In this example, our Codespace would be the box highlighted in red.</p>
<p><img src="/blog/images/csfne/d2-codespace.svg" alt="Codespaces infrastructure image"></p>
<h2 id="codespaces-for-network-engineers-and-educators">Codespaces for Network Engineers and Educators</h2>
<p>I started pondering what a Network Engineer and Educator would need in a Codespace. We would definitely need network nodes in the environment. Reading about concepts is excellent, but seeing them come to life is even more powerful. We should also make the environment easy to use for anyone at any stage of their journey. Because styling is life, it must be pretty. Lastly, we need to be able to present the information to students in something other than a README file (even though I love a good README).</p>
<h2 id="building-a-codespace">Building a Codespace</h2>
<p>Below is an example of our Codespaces (link at the bottom of the blog) project. I&rsquo;ll go over most of the files to give you an idea of how to build an environment like this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">&gt; tree
.
├── .devcontainer
│   ├── devcontainer.json
│   └── Dockerfile
│   └── postCreate.sh
├── ne101
│   └── 01-routing-ospf
│       └── lesson.md
├── startup
│   ├── r1.cfg
│   └── r2.cfg
└── topo.yml
</code></pre></div><h3 id="devcontainerdevcontainerjson">.devcontainer/devcontainer.json</h3>
<p>The .devcontainer directory will host most of our workflow for this environment. For starters, we have a <code>devcontainer.json</code> file. If your project didn&rsquo;t require a lot of extra tooling (maybe a Python project), the file could look something like the following.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json"><span style="color:#75715e">// For format details, see https://aka.ms/devcontainer.json. For config options, see the
</span><span style="color:#75715e"></span>{
  <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Python 3&#34;</span>,
  <span style="color:#f92672">&#34;image&#34;</span>: <span style="color:#e6db74">&#34;mcr.microsoft.com/devcontainers/python:0-3.11-bullseye&#34;</span>,
}
</code></pre></div><p>The example above is using a pre-built container image hosted by Microsoft. In this case, the project is leveraging Python 3.11. The example below provides a more in-depth and realistic view of a <code>.devcontainer.json</code> file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
    <span style="color:#f92672">&#34;build&#34;</span>: { <span style="color:#f92672">&#34;dockerfile&#34;</span>: <span style="color:#e6db74">&#34;Dockerfile&#34;</span> },
    <span style="color:#f92672">&#34;hostRequirements&#34;</span>: {
        <span style="color:#f92672">&#34;cpus&#34;</span>: <span style="color:#ae81ff">4</span>,
        <span style="color:#f92672">&#34;memory&#34;</span>: <span style="color:#e6db74">&#34;16gb&#34;</span>,
        <span style="color:#f92672">&#34;storage&#34;</span>: <span style="color:#e6db74">&#34;32gb&#34;</span>
    },
    <span style="color:#f92672">&#34;containerEnv&#34;</span>: {
        <span style="color:#f92672">&#34;ARISTA_TOKEN&#34;</span>: <span style="color:#e6db74">&#34;${localEnv:ARTOKEN}&#34;</span>,
        <span style="color:#f92672">&#34;CEOS_LAB_VERSION&#34;</span>: <span style="color:#e6db74">&#34;4.32.2.1F&#34;</span>
    },
    <span style="color:#f92672">&#34;features&#34;</span>: {
        <span style="color:#f92672">&#34;ghcr.io/devcontainers/features/github-cli:1&#34;</span>: {},
        <span style="color:#f92672">&#34;ghcr.io/devcontainers/features/docker-in-docker:2&#34;</span>: {}
    },
    <span style="color:#f92672">&#34;secrets&#34;</span>: {
        <span style="color:#f92672">&#34;ARTOKEN&#34;</span>: {
            <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;token to auto-download EOS images from arista.com.&#34;</span>
        }
    },
    <span style="color:#f92672">&#34;customizations&#34;</span>: {
        <span style="color:#f92672">&#34;vscode&#34;</span>: {
            <span style="color:#f92672">&#34;extensions&#34;</span>: [
                <span style="color:#e6db74">&#34;catppuccin.catppuccin-vsc&#34;</span>,
                <span style="color:#e6db74">&#34;catppuccin.catppuccin-vsc-icons&#34;</span>,
                <span style="color:#e6db74">&#34;fabiospampinato.vscode-terminals&#34;</span>,
                <span style="color:#e6db74">&#34;marp-team.marp-vscode&#34;</span>
            ]
        }
    },
    <span style="color:#f92672">&#34;postCreateCommand&#34;</span>: <span style="color:#e6db74">&#34;postCreate.sh&#34;</span>,
    <span style="color:#f92672">&#34;postStartCommand&#34;</span>: <span style="color:#e6db74">&#34;sudo clab deploy -t topo.yml --reconfigure&#34;</span>
}
</code></pre></div><h3 id="build-and-a-dockerfile">build and a Dockerfile</h3>
<p>The first portion of the JSON file calls out a build step. We are not using a pre-built container image to load our environment. We will load a Dockerfile locally for our project to spin up the environment and install the required tooling. We could, in theory, build our container image outside of this workflow and call it with the <code>image</code> key you saw earlier. Below is a quick view of our <code>Dockerfile</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-Docker" data-lang="Docker"><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> mcr.microsoft.com/devcontainers/base:ubuntu</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">ARG</span> CEOS_LAB_VERSION_ARG<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">ENV</span> CEOS_LAB_VERSION<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>CEOS_LAB_VERSION_ARG<span style="color:#e6db74">}</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># install some basic tools inside the container</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apt-get update <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> apt-get install -y --no-install-recommends <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    sshpass <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    iputils-ping <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    htop <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    python3 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    python3-pip <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> rm -rf /var/lib/apt/lists/* <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> rm -Rf /usr/share/doc <span style="color:#f92672">&amp;&amp;</span> rm -Rf /usr/share/man <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> apt-get clean<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># copy postCreate script</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> ./postCreate.sh /bin/postCreate.sh<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> chmod +x /bin/postCreate.sh<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">USER</span><span style="color:#e6db74"> vscode</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># install the latest containerlab and eos-downloader</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> bash -c <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>curl -sL https://get.containerlab.dev<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> pip3 install --user <span style="color:#e6db74">&#34;eos-downloader&gt;=0.10.1&#34;</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span></code></pre></div><p>In summary, this file is performing the following:</p>
<ul>
<li>Start FROM the base Ubuntu image hosted by Microsoft</li>
<li>We use the ARG key to pass data into the build environment (in our case this comes from the <code>.devcontainer.json</code> file)</li>
<li>We then set an ENV variable that will be available within the container (notice that we pass along the ARG we set earlier)</li>
<li>We then RUN a set of commands to install basic tooling like ping and Python3.</li>
<li>We then COPY a file called <code>postCreate.sh</code> (more on this later) and make it executable</li>
<li>Towards the end, we have one more RUN command; this ensures we have <a href="https://containerlab.dev/">Containerlab</a> and <a href="https://github.com/titom73/eos-downloader">eos-downloader</a> installed in our environment</li>
</ul>
<h3 id="hostrequirements">hostRequirements</h3>
<p>When starting a Codespace, you can size it to one of the options given to us by GitHub. The <code>hostRequirements</code> key allows us to set the minimum values required to run this lab. Suppose you don&rsquo;t have the option to use a larger Codespace. In that case, you may need to contact GitHub to give you access to larger images (still free but uses more free credits per month).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">    <span style="color:#e6db74">&#34;hostRequirements&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
        <span style="color:#f92672">&#34;cpus&#34;</span>: <span style="color:#ae81ff">4</span>,
        <span style="color:#f92672">&#34;memory&#34;</span>: <span style="color:#e6db74">&#34;16gb&#34;</span>,
        <span style="color:#f92672">&#34;storage&#34;</span>: <span style="color:#e6db74">&#34;32gb&#34;</span>
    }<span style="color:#960050;background-color:#1e0010">,</span>
</code></pre></div><h3 id="containerenv-and-secrets">containerEnv and secrets</h3>
<p>I&rsquo;ll combine these two sections. <code>contanierEnv</code> sets environment variables to which our container will have access. One is used as a token to download our Arista cEOS images. If you would like to leverage the Codespace in this example, please create an account with a business email on <a href="https://www.arista.com/en/">arista.com</a> (free). The other variable will be used in a future workflow to specify which image we would like to download.</p>
<blockquote>
<p>It&rsquo;s been brought to my attention that Arista customers who register will get a token to download images. Apologies for the confusion.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">    <span style="color:#e6db74">&#34;containerEnv&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
        <span style="color:#f92672">&#34;ARISTA_TOKEN&#34;</span>: <span style="color:#e6db74">&#34;${localEnv:ARTOKEN}&#34;</span>,
        <span style="color:#f92672">&#34;CEOS_LAB_VERSION&#34;</span>: <span style="color:#e6db74">&#34;4.32.2.1F&#34;</span>
    }<span style="color:#960050;background-color:#1e0010">,</span>
    <span style="color:#e6db74">&#34;secrets&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
        <span style="color:#f92672">&#34;ARTOKEN&#34;</span>: {
            <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;token to auto-download EOS images from arista.com.&#34;</span>
        }
    }<span style="color:#960050;background-color:#1e0010">,</span>
</code></pre></div><p>The <code>ARTOKEN</code> secret will be available to our container in future workflows. The screenshot below shows an example before setting any value for <code>ARTOKEN</code>.</p>
<p><img src="/blog/images/csfne/csfne-artoken.png" alt="ARTOKEN set"></p>
<h3 id="features">features</h3>
<p><code>features</code> is another incredible feature of Codespaces and devcontainers. We can tell our environment what extra features we want to install. Think of these as pre-made scripts that handle the installation for us. We add the GitHub CLI feature to simplify our future workflows. Maybe the students aren&rsquo;t Git wizards and need a more straightforward interface to interact with Git. We also install the <code>docker-in-docker</code> feature to allow us to work with Containerlab. Features are incredibly powerful; if you want to see a list of features, please see the <a href="https://containers.dev/features">official documentation</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">    <span style="color:#e6db74">&#34;features&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
        <span style="color:#f92672">&#34;ghcr.io/devcontainers/features/github-cli:1&#34;</span>: {},
        <span style="color:#f92672">&#34;ghcr.io/devcontainers/features/docker-in-docker:2&#34;</span>: {}
    }<span style="color:#960050;background-color:#1e0010">,</span>
</code></pre></div><h3 id="customizationsvscodeextensions">customizations.vscode.extensions</h3>
<p>These extensions allow us to extend the functionality or look of VS Code. I am adding my current favorite theme extension: <code>catppuccin</code>. If you were a University or Company, you could create a custom extension with your own styling. I also load a terminals extension that will give our students the ability to quickly open terminals to the different nodes in the environment. I also install the Marp extension, but more on that later.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">    <span style="color:#e6db74">&#34;customizations&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
        <span style="color:#f92672">&#34;vscode&#34;</span>: {
            <span style="color:#f92672">&#34;extensions&#34;</span>: [
                <span style="color:#e6db74">&#34;catppuccin.catppuccin-vsc&#34;</span>,
                <span style="color:#e6db74">&#34;catppuccin.catppuccin-vsc-icons&#34;</span>,
                <span style="color:#e6db74">&#34;fabiospampinato.vscode-terminals&#34;</span>,
                <span style="color:#e6db74">&#34;marp-team.marp-vscode&#34;</span>
            ]
        }
    }<span style="color:#960050;background-color:#1e0010">,</span>
</code></pre></div><h3 id="vscodesettingsjson">.vscode/settings.json</h3>
<p>We&rsquo;re taking a small break to look at our settings.json file. Again, this can be very deep and complicated, but for this example, we kept it as simple as possible.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
    <span style="color:#f92672">&#34;workbench.colorTheme&#34;</span>: <span style="color:#e6db74">&#34;Catppuccin Frappé&#34;</span>,
    <span style="color:#f92672">&#34;workbench.iconTheme&#34;</span>: <span style="color:#e6db74">&#34;catppuccin-frappe&#34;</span>,
    <span style="color:#f92672">&#34;markdown.marp.themes&#34;</span>: [
        <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/mastern2k3/marpit-nord-theme/master/build/nord-theme.css&#34;</span>
    ]
}
</code></pre></div><p>Here, we set the theme of our VS Code instance and icon theme. We also set our Marp theme (again, explained in a moment). The final version has a few more Marp themes, but that was more for exploration and testing.</p>
<h3 id="vscodeterminalsjson">.vscode/terminals.json</h3>
<p>Another small break, the code block below shows the configuration of our Terminals extension. We set the extension to not autorun and then defined the terminals as a list of dictionaries. We can then provide basic information like the name, icon, color, and command to run on the new terminal.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
    <span style="color:#f92672">&#34;autorun&#34;</span>: <span style="color:#66d9ef">false</span>,
    <span style="color:#f92672">&#34;autokill&#34;</span>: <span style="color:#66d9ef">true</span>,
    <span style="color:#f92672">&#34;terminals&#34;</span>: [
      {
        <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;r1&#34;</span>,
        <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;r1 view&#34;</span>,
        <span style="color:#f92672">&#34;icon&#34;</span>: <span style="color:#e6db74">&#34;code&#34;</span>,
        <span style="color:#f92672">&#34;color&#34;</span>: <span style="color:#e6db74">&#34;terminal.ansiBlue&#34;</span>,
        <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;ssh student@r1&#34;</span>,
        <span style="color:#f92672">&#34;recycle&#34;</span>: <span style="color:#66d9ef">false</span>,
        <span style="color:#f92672">&#34;open&#34;</span>: <span style="color:#66d9ef">true</span>,
        <span style="color:#f92672">&#34;focus&#34;</span>: <span style="color:#66d9ef">true</span>
      },
      {
        <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;r2&#34;</span>,
        <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;r2 view&#34;</span>,
        <span style="color:#f92672">&#34;icon&#34;</span>: <span style="color:#e6db74">&#34;code&#34;</span>,
        <span style="color:#f92672">&#34;color&#34;</span>: <span style="color:#e6db74">&#34;terminal.ansiRed&#34;</span>,
        <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;ssh student@r2&#34;</span>,
        <span style="color:#f92672">&#34;recycle&#34;</span>: <span style="color:#66d9ef">false</span>,
        <span style="color:#f92672">&#34;open&#34;</span>: <span style="color:#66d9ef">true</span>,
        <span style="color:#f92672">&#34;focus&#34;</span>: <span style="color:#66d9ef">false</span>
      }
    ]
}
</code></pre></div><p>The GIF below shows an example of running <code>'cmd/ctrl+alt+t'</code> from VS Code to connect to any pre-defined terminal or running <code>Terminals:run</code> to connect to all defined terminals.</p>
<p><img src="https://media.giphy.com/media/qBY7dCwzj4oqTsIU2T/giphy.gif" alt="Terminals extension GIF"></p>
<h3 id="postcreatecommand">postCreateCommand</h3>
<p>The following portion will run after the container has finished building. Nothing too fancy; we are using eos-downloader to get a specific version of cEOS and have it run the Docker import step. We then immediately tag the image with <code>ceos:latest</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">    <span style="color:#e6db74">&#34;postCreateCommand&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;postCreate.sh&#34;</span><span style="color:#960050;background-color:#1e0010">,</span>
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/usr/bin/env bash
</span><span style="color:#75715e"></span>
set +e

ardl get eos --image-type cEOS --version <span style="color:#e6db74">${</span>CEOS_LAB_VERSION<span style="color:#e6db74">}</span> --import-docker
docker tag arista/ceos:<span style="color:#e6db74">${</span>CEOS_LAB_VERSION<span style="color:#e6db74">}</span> ceos:latest

</code></pre></div><h3 id="poststartcommand">postStartCommand</h3>
<p>We made it to the final portion of the Codespace build, how we deployed the lab. This is no surprise, as we are running the Containerlab deploy command to ensure our cEOS nodes are running. We run it with the <code>--reconfigure</code> flag to ensure we have no issues when the Codespace shuts down and turns back on.</p>
<p>The only thing of note here is that we created startup configurations to set management network stuff in a separate VRF and a student user that did not require passwords to connect. Those can be seen in the <code>startup</code> directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">    <span style="color:#e6db74">&#34;postStartCommand&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;sudo clab deploy -t topo.yml --reconfigure&#34;</span>
</code></pre></div><p>You can see some of the Containerlab output in the GIF above.</p>
<h2 id="slides-with-marp">Slides with Marp</h2>
<p>We are almost there. We now have an environment to learn about networking concepts or even network automation. An instructor needs a tool to present this information. Again, I love a good README, but giving that to a student and telling them &ldquo;good luck&rdquo; is going to have a wide range of results.</p>
<p>In comes Marp, Which tags itself as a Markdown Presentation Ecosystem. That sounds fancy, but at its core, it allows us to write presentations in Markdown format. I wrote about a distant relative, <a href="https://juliopdx.com/2022/11/01/slidev/">slidev</a>, in an earlier blog. For this example, I felt slidev was a bit heavy, and all that I needed to get Marp going was a VS Code extension.</p>
<p>Below is an example of the slides when rendered within the Codespace. To view this presentation, we just hit &ldquo;preview&rdquo; on the markdown file like we would any file in VS Code. You can find this presentation in the <code>ne101/01-routing-ospf/lesson.md</code> file. If you started the Codespace, feel free to follow along with the lesson.</p>
<p><img src="/blog/images/csfne/lesson.png" alt="Example of network engineering lesson rendered with Marp"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far; it really means a lot. I hope you learned a little bit about Codespaces and that it inspires you to create something beautiful in the future. If you have any feedback or improvements, please reach out. All the best!</p>
<ul>
<li><a href="https://unsplash.com/photos/person-pouring-coffee-beans-on-a-machine-6HR8vpjYUHo">Featured Image by Yanapi Senaud</a></li>
<li><a href="https://github.com/codespaces/new?hide_repo_select=true&amp;ref=main&amp;repo=892895632&amp;skip_quickstart=true">Codespace featured in this blog</a></li>
<li><a href="https://github.com/JulioPDX/csfne">GitHub repository</a></li>
<li><a href="https://github.com/aristanetworks/aclabs">Advanced examples of Codespaces on Arista acLabs</a></li>
<li><a href="https://youtu.be/DhsLh1adVcc?si=JYVR-jll5CB8Nhna">Codespaces for Network Engineers video</a></li>
<li><a href="https://pll.harvard.edu/course/cs50-introduction-computer-science">Codespaces leveraged in Harvards CS50 course</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Network CI and Open Source</title>
            <link>https://juliopdx.com/2023/11/25/network-ci-and-open-source/</link>
            <pubDate>Sat, 25 Nov 2023 19:33:45 -0800</pubDate>
            
            <guid>https://juliopdx.com/2023/11/25/network-ci-and-open-source/</guid>
            <description>Introduction Wow, it&amp;rsquo;s been a long time since my last blog post. I recently attended the first AutoCon event focusing on network automation. Long story short, it was terrific. Check it out and be on the lookout for the next one. Shortly after AutoCon, two community members sprung up with sweet blog posts. My colleague and friend Dan Hertzberg are resurrecting the blog. The first post went over leveraging ygot to create Go structs.</description>
            <content type="html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Wow, it&rsquo;s been a long time since my last blog post. I recently attended the first <a href="https://networkautomation.forum/">AutoCon</a> event focusing on network automation. Long story short, it was terrific. Check it out and be on the lookout for the next one. Shortly after AutoCon, two community members sprung up with sweet blog posts. My colleague and friend Dan Hertzberg are resurrecting the blog. The first post went over leveraging <a href="https://danielhertzberg.net/posts/ygotpt1/">ygot</a> to create Go structs. My now in-person friend Danny Wade also made a new post that <a href="https://devnetdan.com/2023/11/21/naf-autocon0-recap/">recapped AutCon</a>. Both are excellent reads and good people; check them out.</p>
<p>Shortly after the event, I wanted to run a simple continuous integration pipeline with Containerlab and some Arista cEOS nodes. Long story short, it worked! However, I had to add a twist to the pipeline by leveraging a GitHub self-hosted runner.</p>
<p>If you&rsquo;ve read my blogs in the past, you may have run into my six-part blog series on <a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/">building a CICD pipeline</a>. Think of this post as a modification or add-on to that previous work. I&rsquo;ll review more tools and provide examples of how they can help you on your network automation journey. I hope you enjoy it.</p>
<h2 id="the-why">The why</h2>
<p>If you got this far, then I&rsquo;ve either hooked you with the introduction, or you&rsquo;re just a fan of my work. Either way, thank you. The main reason for this post is to look at different open-source tools and see how they can be used in our workflows to improve or automate some of the tasks.</p>
<p>One important note! I&rsquo;ll mention a lot of different tools in this blog, don&rsquo;t feel like you have to use all of them. Feel free to explore and choose what works for you and your environment.</p>
<h2 id="requirements">Requirements</h2>
<p>If you want to follow along, you should have a local development machine and a Linux VM or laptop to run the self-hosted runner. In my case, I have it running on a small Linux VM on my Intel NUC (not sponsored). It would be helpful if your development machine has <a href="https://docs.docker.com/engine/install/ubuntu/">Docker installed</a>, but the Linux VM will need it.</p>
<h3 id="github-self-hosted-runner">GitHub self-hosted runner</h3>
<p>GitHub has robust CI tooling within its platform. We can leverage the tooling to create workflows to automate project tasks. These tasks could be auto-assigning team members to issues, testing code, building artifacts, or validating changes on the network.</p>
<p>These &ldquo;actions&rdquo; can run within GitHub&rsquo;s infrastructure to spin up ephemeral environments. This is fantastic, but it could be better when we need this environment to access our containerized network images. The only vendor I am aware of that allows public pulling of their container image is Nokia with their SRLinux platform.</p>
<p>For my setup, I will be using Arista&rsquo;s cEOS images. Instead of hosting the image in a public repository (which I don&rsquo;t think is on the up-and-up), I leveraged the self-hosted runner. Since we manage the self-hosted runner, we can have whatever we want, including container images. More on this in a moment.</p>
<p>GitHub has incredible documentation on <a href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners">installing a self-hosted runner</a>. I won&rsquo;t duplicate that effort, and I&rsquo;ll link you to their excellent docs. One important thing to note is that their documentation walks us through installing the application and running it within the terminal. We need it to run in the background as a service. To do this, make sure you follow <a href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service">GitHub&rsquo;s docs</a> on doing just that. Below is an example output from my VM running the application.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ sudo ./svc.sh status

/etc/systemd/system/actions.runner.JulioPDX-ci-avd-clab.dev.service
● actions.runner.JulioPDX-ci-avd-clab.dev.service - GitHub Actions Runner <span style="color:#f92672">(</span>JulioPDX-ci-avd-clab.dev<span style="color:#f92672">)</span>
     Loaded: loaded <span style="color:#f92672">(</span>/etc/systemd/system/actions.runner.JulioPDX-ci-avd-clab.dev.service; enabled; vendor preset: enabled<span style="color:#f92672">)</span>
     Active: active <span style="color:#f92672">(</span>running<span style="color:#f92672">)</span> since Wed 2023-11-22 12:28:11 PST; <span style="color:#ae81ff">3</span> days ago
   Main PID: <span style="color:#ae81ff">1713</span> <span style="color:#f92672">(</span>runsvc.sh<span style="color:#f92672">)</span>
      Tasks: <span style="color:#ae81ff">23</span> <span style="color:#f92672">(</span>limit: 4915<span style="color:#f92672">)</span>
     Memory: 1.6G
        CPU: 44min 55.008s
     CGroup: /system.slice/actions.runner.JulioPDX-ci-avd-clab.dev.service
             ├─1713 /bin/bash /home/juliopdx/actions-runner/runsvc.sh
             ├─1716 ./externals/node16/bin/node ./bin/RunnerService.js
             └─1724 /home/juliopdx/actions-runner/bin/Runner.Listener run --sta…

Hint: Some lines were ellipsized, use -l to show in full.
</code></pre></div><h3 id="containerlab">Containerlab</h3>
<p><a href="https://containerlab.dev/">Containerlab</a> is my favorite platform for running labs. Whether I&rsquo;m personally learning, testing, or incorporating it into a blog post like this one, the speed and simplicity Containerlab provides are unmatched. If you are following along, install it with this one-liner.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">bash -c <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>curl -sL https://get.containerlab.dev<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</code></pre></div><h3 id="arista-ceos-images">Arista cEOS images</h3>
<p>In one of my earlier <a href="https://juliopdx.com/2021/12/10/my-journey-and-experience-with-containerlab/#how-to-add-networking-images-to-containerlab">blog posts</a>, I talked about how to obtain Arista cEOS images. Make a free account on the site, download whatever version you want, and import the image with Docker. Way before I was an Arista employee, I appreciated that the EOS images were free to obtain; more vendors should do this. A new tool is also available called <a href="https://github.com/titom73/eos-downloader">eos-downloader</a>; it is a command line interface to download Arista EOS images. You should check it out.</p>
<p>That is all you need on the Linux VM running the GitHub self-hosted runner application. At the end of this blog, I&rsquo;ll post a few alternate ways to improve this workflow; feel free to use them as self-learning opportunities.</p>
<h2 id="the-basic-workflow">The basic workflow</h2>
<p>In the most high-level view of our build, we have a local development machine where we can make proposed changes to our test network. The changes in &ldquo;code&rdquo; are then pushed up to GitHub, where automated workflows will run to lint our code, build the configurations, and test it against a short-lived network.</p>
<p><img src="/blog/images/network-ci/d2.svg" alt="Workflow"></p>
<h2 id="but-its-so-much-more">But it&rsquo;s so much more</h2>
<p>If only the world was that simple. A decent amount of tools are available to make this all happen. Feel free to fork or clone the <a href="https://github.com/JulioPDX/ci-avd-clab.git">repository</a> and mess with whatever you want. Below is a view of the folder structure of the repository. Stick with me; I&rsquo;ll do my best to break down every piece.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">git clone https://github.com/JulioPDX/ci-avd-clab.git
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">&gt; tree -I venv
.
├── ansible.cfg
├── anta
│   ├── anta-inv.yml
│   └── anta-test.yml
├── artifacts
│   ├── config_backup
│   ├── documentation
│   ├── intended
│   │   ├── configs
│   │   ├── structured_configs
│   │   └── test_catalogs
│   └── reports
│       └── anta_reports
├── group_vars
│   ├── all.yml
│   ├── ATD_FABRIC.yml
│   ├── ATD_LAB.yml
│   ├── ATD_SERVERS.yml
│   └── ATD_TENANTS_NETWORKS.yml
├── inventory.yml
├── lab.yml
├── LICENSE
├── playbooks
│   ├── build.yml
│   ├── deploy-net.yml
│   ├── validate-net-anta.yml
│   └── validate-net.yml
├── README.md
├── requirements-dev.txt
├── requirements.txt
├── requirements.yml
└── tasks.py
</code></pre></div><h3 id="preparing-the-local-environment">Preparing the local environment</h3>
<p>The repository also includes a <code>.devcontainer</code> file as well. It is handy if you want to develop within a container environment. I&rsquo;ll walk through setting up a local Python development environment. This assumes you have Python installed. To test more in-depth locally, ensure you have Docker, Containerlab, and a cEOS image available.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">cd ci-avd-clab
python3 -m venv venv
source venv/bin/activate
pip install -r requirements-dev.txt
ansible-galaxy collection install -r requirements.yml
</code></pre></div><h3 id="github-actions">GitHub Actions</h3>
<p>GitHub Actions is the CI/CD platform within GitHub. We can leverage GitHub Actions to create automated workflows within our repository. These workflows can be as simple as linting our codebase for any errors or deploying changes to a test network and notifying us of a pass or fail. Below is the entire look at the GitHub Actions file for this example. The file is located in the <code>.github/workflows</code> folder.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .github/workflows/dev.yml</span>
---
<span style="color:#f92672">name</span>: <span style="color:#ae81ff">Network CI</span>

<span style="color:#f92672">on</span>:
  <span style="color:#f92672">pull_request</span>:
    <span style="color:#f92672">branches</span>:
      - <span style="color:#ae81ff">main</span>
  <span style="color:#f92672">push</span>:
    <span style="color:#f92672">branches</span>:
      - <span style="color:#ae81ff">main</span>

<span style="color:#f92672">jobs</span>:
  <span style="color:#f92672">run-linters</span>:
    <span style="color:#f92672">timeout-minutes</span>: <span style="color:#ae81ff">15</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">checkout</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">setup python</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v4</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">run pre-commit</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">pre-commit/action@v3.0.0</span>

  <span style="color:#f92672">dev-build-deploy-test</span>:
    <span style="color:#f92672">needs</span>: <span style="color:#ae81ff">run-linters</span>
    <span style="color:#f92672">env</span>:
      <span style="color:#f92672">ANTA_PASSWORD</span>: <span style="color:#ae81ff">${{ secrets.ANTA_PASSWORD }}</span>
      <span style="color:#f92672">SUDO_PASS</span>: <span style="color:#ae81ff">${{ secrets.SUDO_PASS }}</span>
      <span style="color:#f92672">CEOS_VERSION</span>: <span style="color:#ae81ff">4.30</span><span style="color:#ae81ff">.2F</span>

    <span style="color:#f92672">timeout-minutes</span>: <span style="color:#ae81ff">15</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">avd-ci</span>
    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">checkout</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">setup Python</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v4</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">python-version</span>: <span style="color:#e6db74">&#34;3.9&#34;</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">install requirements</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">pip install invoke &amp;&amp; invoke setup-base</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">create topology with containerlab</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">echo $SUDO_PASS | sudo -S containerlab deploy -t lab.yml --reconfigure</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">build with AVD</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">ansible-playbook playbooks/build.yml</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">deploy configuration to nodes</span>
        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">          ansible-playbook playbooks/deploy-net.yml
</span><span style="color:#e6db74">          sleep 10</span>          

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">run ANTA cli tests</span>
        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">          anta \
</span><span style="color:#e6db74">              --username admin \
</span><span style="color:#e6db74">              --password $ANTA_PASSWORD \
</span><span style="color:#e6db74">              --inventory anta/anta-inv.yml \
</span><span style="color:#e6db74">              --log-file artifacts/anta.log \
</span><span style="color:#e6db74">              nrfu --catalog anta/anta-test.yml text</span>          

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">validate configuration on nodes</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">ansible-playbook playbooks/validate-net.yml</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">validate configuration on nodes w/anta</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">ansible-playbook playbooks/validate-net-anta.yml</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">destroy lab</span>
        <span style="color:#f92672">if</span>: <span style="color:#ae81ff">always()</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">echo $SUDO_PASS | sudo -S containerlab destroy -t lab.yml -c</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">cleanup self hosted runner</span>
        <span style="color:#f92672">if</span>: <span style="color:#ae81ff">always()</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">TooMuch4U/actions-clean@v2.1</span>
</code></pre></div><p>I&rsquo;ll break this down piece by piece and show you most of the tools from my local development machine along the way.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .github/workflows/dev.yml</span>
---
<span style="color:#f92672">name</span>: <span style="color:#ae81ff">Network CI</span>

<span style="color:#f92672">on</span>:
  <span style="color:#f92672">pull_request</span>:
    <span style="color:#f92672">branches</span>:
      - <span style="color:#ae81ff">main</span>
  <span style="color:#f92672">push</span>:
    <span style="color:#f92672">branches</span>:
      - <span style="color:#ae81ff">main</span>
...
</code></pre></div><p>We define a <code>name</code> for the workflow at the root of the GitHub actions file. This doesn&rsquo;t have any impact on the workflow itself, but it&rsquo;s always helpful to pick a name with meaning. The <code>on</code> key is where things start to get interesting. Here, we can specify what events and branches we would like this workflow to run on.</p>
<p>Currently, we have it running on a pull request event or a push event, both heading towards the main branch. This definition is not something to use in a production environment. You would likely have a workflow file for your feature/fix branches and another for the main branch (the production network).</p>
<h3 id="jobs-linters-and-pre-commit">Jobs, Linters, and pre-commit</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .github/workflows/dev.yml</span>
...
<span style="color:#f92672">jobs</span>:
  <span style="color:#f92672">run-linters</span>:
    <span style="color:#f92672">timeout-minutes</span>: <span style="color:#ae81ff">15</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">checkout</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">setup python</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v4</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">run pre-commit</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">pre-commit/action@v3.0.0</span>
...
</code></pre></div><p><code>jobs</code> in GitHub actions define the individual steps we would like performed for a particular&hellip; job. In our example, we have one job that handles the linting of the code base and another that deals with building and testing a virtual network. Right below the job key is the unique name of <code>run-linters</code>, which is user-defined, but again, it should make sense. We then have a <code>runs-on</code> key. The <code>runs-on</code> key defines what platform we would like under the hood for this workflow. You could have a macOS, Windows, or Linux environment. This could also be on the GitHub infrastructure or self-hosted.</p>
<p>The <code>run-linters</code> job doesn&rsquo;t need access to our self-hosted runner or the container images. It only needs access to the repository. To save on my personal resources, this job uses the <code>ubuntu-latest</code> variant of a GitHub runner (the node performing the steps), which runs on GitHub&rsquo;s infrastructure. Another thing to note: the <code>timeout-minutes</code> will end the workflow if we exceed that time. This workflow should be far from that, so hitting that limit should signal that we have an issue.</p>
<p>The <code>steps</code> in actions are the units of work we would like performed. We jokingly called them a collection of bash scripts that do a thing. The first two steps are standard in Python projects; we use <code>checkout</code> to make the repository available to the runner and <code>setup-python</code> to ensure Python is in our PATH.</p>
<h4 id="pre-commit">pre-commit</h4>
<p>The last step in this job is to run pre-commit. Pre-commit allows us to automate processes before or during the commit process in our development workflow. For example, we could run linters to check for extra new lines in files, lint YAML, or maybe even Python code.</p>
<p>Pre-commit works by defining a <code>.pre-commit.yaml</code> file at the root of our project. Below is a view of the local one from the repository. Automating these processes enables not only yourself but contributors to your project. There is no question on what rules to follow or how Python code should look, &ldquo;just run pre-commit.&rdquo;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .pre-commit-config.yaml</span>
---
<span style="color:#f92672">repos</span>:
  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/pre-commit/pre-commit-hooks</span>
    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v4.5.0</span>
    <span style="color:#f92672">hooks</span>:
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">check-yaml</span>
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">end-of-file-fixer</span>
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">trailing-whitespace</span>
  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/ansible/ansible-lint</span>
    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v6.22.0</span>
    <span style="color:#f92672">hooks</span>:
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">ansible-lint</span>
  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/adrienverge/yamllint.git</span>
    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v1.33.0</span>
    <span style="color:#f92672">hooks</span>:
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">yamllint</span>
  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/DavidAnson/markdownlint-cli2</span>
    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v0.11.0</span>
    <span style="color:#f92672">hooks</span>:
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">markdownlint-cli2</span>
  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/astral-sh/ruff-pre-commit</span>
    <span style="color:#75715e"># Ruff version.</span>
    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v0.1.6</span>
    <span style="color:#f92672">hooks</span>:
      <span style="color:#75715e"># Run the linter.</span>
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">ruff</span>
      <span style="color:#75715e"># Run the formatter.</span>
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">ruff-format</span>
</code></pre></div><p>The pre-commit configuration for each tool is mostly the same; some have additional available arguments. In the configuration file, we set the <code>repo</code> location, the <code>rev</code> or version we want, and what <code>hook</code> from the repository. Many hooks can exist in a single repository. For more examples, check out the list of <a href="https://pre-commit.com/hooks.html">available pre-commit hooks</a> (not all-encompassing).</p>
<p>There are two options when running pre-commit. You can add it to your git hooks(<code>pre-commit install</code>), and it will run anytime you try and commit, or you can run it manually (<code>pre-commit run -a</code>). Below is a quick example of pre-commit in action.</p>
<p>The following shows some user errors in files and pre-commit attempts to correct them.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">&gt; pre-commit run -a
check yaml...............................................................Passed
fix end of files.........................................................Failed
- hook id: end-of-file-fixer
- exit code: <span style="color:#ae81ff">1</span>
- files were modified by this hook

Fixing lab.yml
Fixing group_vars/ATD_TENANTS_NETWORKS.yml

trim trailing whitespace.................................................Passed
Ansible-lint.............................................................Passed
yamllint.................................................................Passed
markdownlint-cli2........................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook

<span style="color:#ae81ff">1</span> file reformatted
</code></pre></div><p>The following is an example of pre-commit passing all checks.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">&gt; pre-commit run -a
check yaml...............................................................Passed
fix end of files.........................................................Passed
trim trailing whitespace.................................................Passed
Ansible-lint.............................................................Passed
yamllint.................................................................Passed
markdownlint-cli2........................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Passed
</code></pre></div><p>Feel free to explore the documentation of the various linters used and see if any others will benefit you in your workflows.</p>
<h4 id="the-second-job">The second job</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .github/workflows/dev.yml</span>
...
  <span style="color:#f92672">dev-build-deploy-test</span>:
    <span style="color:#f92672">needs</span>: <span style="color:#ae81ff">run-linters</span>
    <span style="color:#f92672">env</span>:
      <span style="color:#f92672">ANTA_PASSWORD</span>: <span style="color:#ae81ff">${{ secrets.ANTA_PASSWORD }}</span>
      <span style="color:#f92672">SUDO_PASS</span>: <span style="color:#ae81ff">${{ secrets.SUDO_PASS }}</span>
      <span style="color:#f92672">CEOS_VERSION</span>: <span style="color:#ae81ff">4.30</span><span style="color:#ae81ff">.2F</span>

    <span style="color:#f92672">timeout-minutes</span>: <span style="color:#ae81ff">15</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">avd-ci</span>
...
</code></pre></div><p>Moving down the actions file, we now have our second job. Again, we have a name, but now you can see a new key called <code>needs</code>. This key tells this job to wait for the <code>run-linters</code> job to complete before it runs. We also specify three environment variables we would like available to the runner. The <code>ANTA_PASSWORD</code> reflects the password used to connect to EOS nodes in future steps. <code>SUDO_PASS</code> is used to pass along the credential when running Containerlab. These are within the GitHub actions' secrets; set them under the secrets view.</p>
<p><img src="/blog/images/network-ci/secrets.png" alt="&ldquo;Setting GitHub actions secrets&rdquo;"></p>
<p>The last variable specifies the version of cEOS I&rsquo;m using, which is then referenced in the Containerlab topology file. A preview of that is below. In my case, the image is tagged as <code>ceos:4.30.2F</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">...
<span style="color:#f92672">topology</span>:
  <span style="color:#f92672">kinds</span>:
    <span style="color:#f92672">ceos</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:${CEOS_VERSION:=4.30.2F}</span>
...
</code></pre></div><p>The end of the workflow file above mentions a new <code>runs-on</code> value. Installing the self-hosted application instructs us to set a tag or use the default. Below is a view of my runner with the <code>avd-ci</code> tag available. Your installation will be different.</p>
<p><img src="/blog/images/network-ci/runner-done.png" alt="&ldquo;View of runner in GitHub actions&rdquo;"></p>
<h4 id="invoke-and-containerlab">Invoke and Containerlab</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .github/workflows/dev.yml</span>
...
    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">checkout</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">setup Python</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v4</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">python-version</span>: <span style="color:#e6db74">&#34;3.9&#34;</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">install requirements</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">pip install invoke &amp;&amp; invoke setup-base</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">create topology with containerlab</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">echo $SUDO_PASS | sudo -S containerlab deploy -t lab.yml --reconfigure</span>
...
</code></pre></div><p>Top-level portions are similar to our first job; the main difference is that we are specifying a specific Python version that should be in our PATH this time. Similar to my earlier comment that pre-commit hooks have additional arguments, action steps also have them.</p>
<p>The last two steps use the <code>run</code> variant. This simply means &ldquo;run the following commands on the runner.&rdquo; Invoke was new for me on this journey; I like to think of Invoke as a more Pythonic replacement for Makefiles. Invoke allows us to automate tasks as well by defining them in Python. Below is an example of the <code>setup_base</code> function, which leverages two other functions; <code>install_python_reqs</code> and <code>install_ansible_collections</code>. We can use this to ensure our self-hosted runner has all our requirements installed.</p>
<p>The last step listed shows the use of our <code>SUDO_PASS</code> environment variable; we need the variable to run Containerlab as sudo. Please let me know if you know of a better or easier way to make this work. The Containerlab command always runs with the <code>--reconfigure</code> flag. We basically always want the environment to be a fresh deployment. You could also build this to never shut down the test environment; that would remove the build times in the workflow.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># tasks.py</span>
<span style="color:#f92672">...</span>
<span style="color:#a6e22e">@task</span>()
<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">setup_base</span>(ctx):
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Set up the development container environment.
</span><span style="color:#e6db74">    This task runs other tasks to install various requirements.
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    <span style="color:#75715e"># Install Python project and development requirements</span>
    install_python_reqs(ctx)

    <span style="color:#75715e"># Install Ansible collection requirements</span>
    install_ansible_collections(ctx)


<span style="color:#a6e22e">@task</span>()
<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">install_python_reqs</span>(ctx):
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Install project Python requirements.
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
    print(<span style="color:#e6db74">&#34;Installing Project Python Requirements&#34;</span>)
    ctx<span style="color:#f92672">.</span>run(<span style="color:#e6db74">&#34;pip install --upgrade pip&#34;</span>)
    ctx<span style="color:#f92672">.</span>run(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;pip install -r </span><span style="color:#e6db74">{</span>REQUIREMENTS_FILE_PYTHON_DEV<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)


<span style="color:#a6e22e">@task</span>()
<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">install_ansible_collections</span>(ctx):
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Install Ansible collection requirements, including Arista AVD collection.
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
    print(<span style="color:#e6db74">&#34;Installing Project Ansible Collection Requirements&#34;</span>)
    ctx<span style="color:#f92672">.</span>run(
        <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;ansible-galaxy collection install -r </span><span style="color:#e6db74">{</span>REQUIREMENTS_FILE_ANSIBLE<span style="color:#e6db74">}</span><span style="color:#e6db74"> --force-with-deps&#34;</span>
    )
<span style="color:#f92672">...</span>
</code></pre></div><p>A view of our lab file is below. It&rsquo;s a single spine and two leaf deployment.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># lab.yml</span>
---
<span style="color:#f92672">name</span>: <span style="color:#ae81ff">ci-avd</span>
<span style="color:#f92672">prefix</span>: <span style="color:#e6db74">&#34;&#34;</span>

<span style="color:#f92672">mgmt</span>:
  <span style="color:#f92672">network</span>: <span style="color:#ae81ff">statics</span>
  <span style="color:#f92672">ipv4-subnet</span>: <span style="color:#ae81ff">172.100.100.0</span><span style="color:#ae81ff">/24</span>

<span style="color:#f92672">topology</span>:
  <span style="color:#f92672">kinds</span>:
    <span style="color:#f92672">ceos</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:${CEOS_VERSION:=4.30.2F}</span>
  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">spine1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt-ipv4</span>: <span style="color:#ae81ff">172.100.100.11</span>
    <span style="color:#f92672">leaf1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt-ipv4</span>: <span style="color:#ae81ff">172.100.100.13</span>
    <span style="color:#f92672">leaf2</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt-ipv4</span>: <span style="color:#ae81ff">172.100.100.14</span>
  <span style="color:#f92672">links</span>:
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;spine1:eth1&#34;</span>, <span style="color:#e6db74">&#34;leaf1:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;spine1:eth2&#34;</span>, <span style="color:#e6db74">&#34;leaf2:eth1&#34;</span>]

</code></pre></div><p>If you are following along or would like to test locally, feel free to spin up the lab</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo clab deploy -t lab.yml
</code></pre></div><h4 id="arista-validated-designs">Arista Validated Designs</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .github/workflows/dev.yml</span>
...
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">build with AVD</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">ansible-playbook playbooks/build.yml</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">deploy configuration to nodes</span>
        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">          ansible-playbook playbooks/deploy-net.yml
</span><span style="color:#e6db74">          sleep 10</span>          
...
</code></pre></div><p><a href="https://avd.arista.com/4.4/index.html">AVD</a> is an interesting framework. It allows us to define our network using abstracted data models. For example, if I want to add a new VLAN or VRF, I would define that in a common YAML file related to the nodes in my topology. I may want to define common services like NTP, DNS, etc. I would do that from a common or specific (depending on the use case) YAML file. For example, run the following Ansible playbook to see what AVD accomplishes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">ansible-playbook playbooks/build.yml
</code></pre></div><p>The definitions for this deployment are defined under the <code>group_vars</code> directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># playbooks/build.yml</span>
---
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build EOS config</span>
  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">ATD_FABRIC</span>
  <span style="color:#f92672">connection</span>: <span style="color:#ae81ff">local</span>
  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">false</span>

  <span style="color:#f92672">tasks</span>:
    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Generate intended variables</span>
      <span style="color:#f92672">ansible.builtin.import_role</span>:
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">arista.avd.eos_designs</span>

    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Generate device intended config and documentation</span>
      <span style="color:#f92672">ansible.builtin.import_role</span>:
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">arista.avd.eos_cli_config_gen</span>
</code></pre></div><p>Please note every CLI output in this blog is in real-time.</p>
<p><a href="https://asciinema.org/a/624520"><img src="https://asciinema.org/a/624520.svg" alt="asciicast AVD ansible"></a></p>
<p>AVD leverages roles within Ansible to abstract away a lot of the complexity. The neat thing is that we have complete running and structured configurations (in YAML) per device and site documentation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">├── artifacts
│   ├── config_backup
│   ├── documentation
│   │   ├── ATD_FABRIC
│   │   │   ├── ATD_FABRIC-documentation.md
│   │   │   ├── ATD_FABRIC-p2p-links.csv
│   │   │   └── ATD_FABRIC-topology.csv
│   │   └── devices
│   │       ├── leaf1.md
│   │       ├── leaf2.md
│   │       └── spine1.md
│   ├── intended
│   │   ├── configs
│   │   │   ├── leaf1.cfg
│   │   │   ├── leaf2.cfg
│   │   │   └── spine1.cfg
│   │   ├── structured_configs
│   │   │   ├── leaf1.yml
│   │   │   ├── leaf2.yml
│   │   │   └── spine1.yml
</code></pre></div><p>Feel free to review the generated artifacts or modify the <code>ATD_TENANTS_NETOWRKS.yml</code> file with an additional SVI and rerun the build playbook.</p>
<h5 id="the-bonus-example">The bonus example</h5>
<p>When AVD was first created, it was available as an Ansible collection; now we can leverage the Python implementation of  AVD using <code>pyavd</code>. A reasonably rough Python script is in the repository if you want to test it. Please note <code>pyavd</code> is in beta, so your mileage may vary.</p>
<p>You may need to install <code>ansible</code> instead of only installing <code>ansible-core</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">pip install ansible rich pyavd
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># avd-py.py</span>
<span style="color:#f92672">import</span> copy
<span style="color:#f92672">import</span> difflib
<span style="color:#f92672">import</span> io
<span style="color:#f92672">import</span> sys
<span style="color:#f92672">from</span> ansible.inventory.manager <span style="color:#f92672">import</span> InventoryManager
<span style="color:#f92672">from</span> ansible.parsing.dataloader <span style="color:#f92672">import</span> DataLoader
<span style="color:#f92672">from</span> ansible.vars.manager <span style="color:#f92672">import</span> VariableManager
<span style="color:#f92672">from</span> rich.progress <span style="color:#f92672">import</span> Progress
<span style="color:#f92672">from</span> pyavd <span style="color:#f92672">import</span> (
    get_avd_facts,
    get_device_structured_config,
    get_device_config,
    validate_inputs,
)
<span style="color:#f92672">from</span> rich <span style="color:#f92672">import</span> print


<span style="color:#75715e"># Recreating the inventory objects to be like  Ansible</span>
dl <span style="color:#f92672">=</span> DataLoader()
im <span style="color:#f92672">=</span> InventoryManager(loader<span style="color:#f92672">=</span>dl, sources<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;inventory.yml&#34;</span>])
vm <span style="color:#f92672">=</span> VariableManager(loader<span style="color:#f92672">=</span>dl, inventory<span style="color:#f92672">=</span>im)

hosts <span style="color:#f92672">=</span> {}

<span style="color:#75715e"># grab all the hosts and add the hostname with relevant</span>
<span style="color:#75715e"># variables to the hosts dictionary</span>
<span style="color:#75715e"># deep copy required to make sure information is not overwritten</span>
<span style="color:#66d9ef">for</span> host <span style="color:#f92672">in</span> im<span style="color:#f92672">.</span>get_hosts(<span style="color:#e6db74">&#34;all&#34;</span>):
    host_vars <span style="color:#f92672">=</span> vm<span style="color:#f92672">.</span>get_vars(host<span style="color:#f92672">=</span>host)
    hosts[str(host)] <span style="color:#f92672">=</span> copy<span style="color:#f92672">.</span>deepcopy(host_vars)

<span style="color:#66d9ef">with</span> Progress() <span style="color:#66d9ef">as</span> progress:
    <span style="color:#75715e"># progress bars for pretty output</span>
    total <span style="color:#f92672">=</span> len(hosts)
    task1 <span style="color:#f92672">=</span> progress<span style="color:#f92672">.</span>add_task(<span style="color:#e6db74">&#34;[red]facts...&#34;</span>, total<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)
    task2 <span style="color:#f92672">=</span> progress<span style="color:#f92672">.</span>add_task(<span style="color:#e6db74">&#34;[blue]validate inputs...&#34;</span>, total<span style="color:#f92672">=</span>total)
    task3 <span style="color:#f92672">=</span> progress<span style="color:#f92672">.</span>add_task(<span style="color:#e6db74">&#34;[yellow]structured config...&#34;</span>, total<span style="color:#f92672">=</span>total)
    task4 <span style="color:#f92672">=</span> progress<span style="color:#f92672">.</span>add_task(<span style="color:#e6db74">&#34;[green]build config...&#34;</span>, total<span style="color:#f92672">=</span>total)

    facts <span style="color:#f92672">=</span> get_avd_facts(hosts)
    progress<span style="color:#f92672">.</span>update(task1, advance<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)

    <span style="color:#75715e"># loop to check the state of validate status and print</span>
    <span style="color:#75715e"># any errors</span>
    <span style="color:#66d9ef">for</span> k, v <span style="color:#f92672">in</span> hosts<span style="color:#f92672">.</span>items():
        validate <span style="color:#f92672">=</span> validate_inputs(v)

        <span style="color:#66d9ef">if</span> validate<span style="color:#f92672">.</span>failed:
            print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;The following validation errors were seen for </span><span style="color:#e6db74">{</span>k<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
            <span style="color:#66d9ef">for</span> issue <span style="color:#f92672">in</span> validate<span style="color:#f92672">.</span>validation_errors:
                print(issue)
            sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
        progress<span style="color:#f92672">.</span>update(task2, advance<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)

        struct_conf <span style="color:#f92672">=</span> get_device_structured_config(k, v, facts)
        progress<span style="color:#f92672">.</span>update(task3, advance<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)

        config <span style="color:#f92672">=</span> get_device_config(struct_conf)
        buf <span style="color:#f92672">=</span> io<span style="color:#f92672">.</span>StringIO(config)
        print(k)
        <span style="color:#66d9ef">try</span>:
            <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;artifacts/intended/configs/</span><span style="color:#e6db74">{</span>k<span style="color:#e6db74">}</span><span style="color:#e6db74">.cfg&#34;</span>) <span style="color:#66d9ef">as</span> file:
                old <span style="color:#f92672">=</span> file<span style="color:#f92672">.</span>readlines()

            <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> difflib<span style="color:#f92672">.</span>unified_diff(old, buf<span style="color:#f92672">.</span>readlines(), lineterm<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&#34;</span>):
                print(line)

            <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;artifacts/intended/configs/</span><span style="color:#e6db74">{</span>k<span style="color:#e6db74">}</span><span style="color:#e6db74">.cfg&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> f:
                f<span style="color:#f92672">.</span>writelines(config)

        <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">FileNotFoundError</span>:
            <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;artifacts/intended/configs/</span><span style="color:#e6db74">{</span>k<span style="color:#e6db74">}</span><span style="color:#e6db74">.cfg&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> f:
                f<span style="color:#f92672">.</span>writelines(config)

        progress<span style="color:#f92672">.</span>update(task4, advance<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)

</code></pre></div><p><a href="https://asciinema.org/a/624519"><img src="https://asciinema.org/a/624519.svg" alt="asciicast AVD Python"></a></p>
<p>The last step deals with running a configuration replacement on our nodes from the generated configurations.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># playbooks/deploy-net.yml</span>
---
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build EOS config</span>
  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">ATD_FABRIC</span>
  <span style="color:#f92672">connection</span>: <span style="color:#ae81ff">local</span>
  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">false</span>

  <span style="color:#f92672">tasks</span>:
    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Generate intended variables</span>
      <span style="color:#f92672">ansible.builtin.import_role</span>:
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">arista.avd.eos_config_deploy_eapi</span>

</code></pre></div><p><a href="https://asciinema.org/a/624523"><img src="https://asciinema.org/a/624523.svg" alt="asciicast"></a></p>
<p>I don&rsquo;t have a Python configuration replacement example but feel free to experiment and create one. You could always use something like <a href="https://github.com/napalm-automation/napalm">NAPALM</a>, <a href="https://github.com/ktbyers/netmiko">Netmiko</a>, or <a href="https://github.com/carlmontanari/scrapli">Scrapli</a>.</p>
<h3 id="avd-validate-state--arista-network-test-automation-anta">AVD validate state &amp; Arista Network Test Automation (ANTA)</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">...
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">run ANTA cli tests</span>
        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">          anta \
</span><span style="color:#e6db74">              --username admin \
</span><span style="color:#e6db74">              --password $ANTA_PASSWORD \
</span><span style="color:#e6db74">              --inventory anta/anta-inv.yml \
</span><span style="color:#e6db74">              --log-file artifacts/anta.log \
</span><span style="color:#e6db74">              nrfu --catalog anta/anta-test.yml text</span>          

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">validate configuration on nodes</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">ansible-playbook playbooks/validate-net.yml</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">validate configuration on nodes w/anta</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">ansible-playbook playbooks/validate-net-anta.yml</span>
...
</code></pre></div><p>AVD has a role to validate the state of our deployed network. Below is an example of the playbook and the output. The validate state role will also produce reports on passing or failing tests.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">ansible-playbook playbooks/validate-net.yml
</code></pre></div><p><a href="https://asciinema.org/a/624526"><img src="https://asciinema.org/a/624526.svg" alt="asciicast"></a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">artifacts/reports
├── anta_reports
├── ATD_FABRIC-state.csv
└── ATD_FABRIC-state.md
</code></pre></div><p><a href="https://www.anta.ninja/stable/">ANTA</a> is the new kid on the block for network testing. There are a few ways to implement ANTA. We can use it directly in Python, leverage the CLI tool, or in a preview release within AVD Ansible. The ANTA AVD variant makes things fairly automated as far as test definitions. We basically set the <code>use_anta</code> variable to <code>true</code>. Below is the view of the playbook and the output.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Validate deployed config</span>
  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">ATD_FABRIC</span>
  <span style="color:#f92672">connection</span>: <span style="color:#ae81ff">httpapi</span>
  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">false</span>

  <span style="color:#f92672">vars</span>:
    <span style="color:#f92672">use_anta</span>: <span style="color:#66d9ef">true</span>

  <span style="color:#f92672">tasks</span>:
    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Validate EOS state with ANTA</span>
      <span style="color:#f92672">ansible.builtin.import_role</span>:
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">arista.avd.eos_validate_state</span>

</code></pre></div><p>Below is the output from the ANTA validate playbook.</p>
<p><a href="https://asciinema.org/a/624528"><img src="https://asciinema.org/a/624528.svg" alt="asciicast"></a></p>
<p>Below is a quick example of one of the generated artifacts.</p>
<p><a href="https://asciinema.org/a/624530"><img src="https://asciinema.org/a/624530.svg" alt="asciicast"></a></p>
<p>ANTA also has a CLI, which requires two main parts: an inventory definition and a test catalog.</p>
<p>Here&rsquo;s a view of our inventory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># anta/anta-inv.yml</span>
---
<span style="color:#f92672">anta_inventory</span>:
  <span style="color:#f92672">hosts</span>:
    - <span style="color:#f92672">host</span>: <span style="color:#ae81ff">172.100.100.11</span>
      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">spine1</span>
      <span style="color:#f92672">tags</span>: [<span style="color:#e6db74">&#34;fabric&#34;</span>, <span style="color:#e6db74">&#34;spine&#34;</span>]
    - <span style="color:#f92672">host</span>: <span style="color:#ae81ff">172.100.100.13</span>
      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">leaf1</span>
      <span style="color:#f92672">tags</span>: [<span style="color:#e6db74">&#34;fabric&#34;</span>, <span style="color:#e6db74">&#34;leaf&#34;</span>, <span style="color:#e6db74">&#34;pod1&#34;</span>]
    - <span style="color:#f92672">host</span>: <span style="color:#ae81ff">172.100.100.14</span>
      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">leaf2</span>
      <span style="color:#f92672">tags</span>: [<span style="color:#e6db74">&#34;fabric&#34;</span>, <span style="color:#e6db74">&#34;leaf&#34;</span>, <span style="color:#e6db74">&#34;pod2&#34;</span>]

</code></pre></div><p>Here&rsquo;s a look at the test catalog. These are YAML representations of the Python classes in the code. Head to the <a href="https://www.anta.ninja/stable/api/tests/">documentation</a> to see what&rsquo;s available. I&rsquo;ll show an example of a pass and a fail.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">anta.tests.software</span>:
  <span style="color:#75715e"># Verifies the device is running one of the allowed EOS version.</span>
  - <span style="color:#f92672">VerifyEOSVersion</span>:
      <span style="color:#75715e"># List of allowed EOS versions.</span>
      <span style="color:#f92672">versions</span>:
        - <span style="color:#ae81ff">4.30</span><span style="color:#ae81ff">.2F-33092737.4302F (engineering build)</span>

<span style="color:#f92672">anta.tests.routing.generic</span>:
  - <span style="color:#f92672">VerifyRoutingProtocolModel</span>:
      <span style="color:#f92672">model</span>: <span style="color:#e6db74">&#34;multi-agent&#34;</span>

<span style="color:#f92672">anta.tests.routing.bgp</span>:
  - <span style="color:#f92672">VerifyBGPPeersHealth</span>:
      <span style="color:#f92672">address_families</span>:
        - <span style="color:#f92672">afi</span>: <span style="color:#ae81ff">ipv4</span>
          <span style="color:#f92672">safi</span>: <span style="color:#ae81ff">unicast</span>

<span style="color:#f92672">anta.tests.vxlan</span>:
  - <span style="color:#f92672">VerifyVxlan1Interface</span>:
  - <span style="color:#f92672">VerifyVxlanConfigSanity</span>:

</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">export ANTA_PASSWORD<span style="color:#f92672">=</span>somesecretpassword
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">anta <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>--username admin <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>--password $ANTA_PASSWORD <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>--inventory anta/anta-inv.yml <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>--log-file artifacts/anta.log <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>nrfu --catalog anta/anta-test.yml table
</code></pre></div><p><a href="https://asciinema.org/a/624535"><img src="https://asciinema.org/a/624535.svg" alt="asciicast"></a></p>
<p>I&rsquo;ll slightly edit by modifying the Routing Protocol Model.</p>
<p><a href="https://asciinema.org/a/624536"><img src="https://asciinema.org/a/624536.svg" alt="asciicast"></a></p>
<h3 id="cleaning-the-self-hosted-runner">Cleaning the self-hosted runner</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># .github/workflows/dev.yml</span>
...
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">destroy lab</span>
        <span style="color:#f92672">if</span>: <span style="color:#ae81ff">always()</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">echo $SUDO_PASS | sudo -S containerlab destroy -t lab.yml -c</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">cleanup self hosted runner</span>
        <span style="color:#f92672">if</span>: <span style="color:#ae81ff">always()</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">TooMuch4U/actions-clean@v2.1</span>
</code></pre></div><p>The GitHub runners are usually ephemeral; we don&rsquo;t have to worry about cleanup. The self-hosted runner is different since the environment can last indefinitely. We use these last steps to clean up files we don&rsquo;t need and destroy our topology. Please note the new key of <code>if</code>; we want these steps to run no matter what happens in the pipeline run.</p>
<h2 id="run-the-pipeline">Run the Pipeline</h2>
<p>Wow, that was a lot of information. Please let me know if you have any questions or feel something needs clarification. I&rsquo;ll add a new service to the network by editing the <code>group_vars/ATD_TENANTS_NETWORKS.yml</code> file and then pushing it up to the repository, which should kick off a pipeline run.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># ATD_TENANTS_NETWORKS.yml</span>
---
<span style="color:#f92672">tenants</span>:
  <span style="color:#75715e"># Tenant A Specific Information - VRFs / VLANs</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Tenant_A</span>
    <span style="color:#f92672">mac_vrf_vni_base</span>: <span style="color:#ae81ff">10000</span>
    <span style="color:#f92672">vrfs</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Tenant_A_OP_Zone</span>
        <span style="color:#f92672">vrf_vni</span>: <span style="color:#ae81ff">10</span>
        <span style="color:#f92672">vtep_diagnostic</span>:
          <span style="color:#f92672">loopback</span>: <span style="color:#ae81ff">100</span>
          <span style="color:#f92672">loopback_ip_range</span>: <span style="color:#ae81ff">10.255.1.0</span><span style="color:#ae81ff">/24</span>
        <span style="color:#f92672">svis</span>:
          - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">110</span>
            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Tenant_A_OP_Zone_1</span>
            <span style="color:#f92672">tags</span>: [<span style="color:#ae81ff">opzone]</span>
            <span style="color:#f92672">enabled</span>: <span style="color:#66d9ef">true</span>
            <span style="color:#f92672">ip_address_virtual</span>: <span style="color:#ae81ff">10.1.10.1</span><span style="color:#ae81ff">/24</span>
          - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">111</span>
            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Tenant_A_OP_Zone_2</span>
            <span style="color:#f92672">tags</span>: [<span style="color:#ae81ff">opzone]</span>
            <span style="color:#f92672">enabled</span>: <span style="color:#66d9ef">true</span>
            <span style="color:#f92672">ip_address_virtual</span>: <span style="color:#ae81ff">10.1.11.1</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">l2vlans</span>:
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">160</span>
        <span style="color:#f92672">vni_override</span>: <span style="color:#ae81ff">55160</span>
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Tenant_A_VMOTION</span>
        <span style="color:#f92672">tags</span>: [<span style="color:#ae81ff">vmotion]</span>

</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">git add .
git commit -m <span style="color:#e6db74">&#34;something&#34;</span>
git push
</code></pre></div><p><img src="/blog/images/network-ci/linters-complete.png" alt="&ldquo;View of linters running&rdquo;"></p>
<p><img src="/blog/images/network-ci/deploy-lab-runner.png" alt="&ldquo;View of runner in GitHub actions&rdquo;"></p>
<p><img src="/blog/images/network-ci/building.png" alt="&ldquo;View of runner in GitHub actions&rdquo;"></p>
<p><img src="/blog/images/network-ci/anta-cli.png" alt="&ldquo;View of runner in GitHub actions&rdquo;"></p>
<p><img src="/blog/images/network-ci/complete.png" alt="&ldquo;View of runner in GitHub actions&rdquo;"></p>
<h2 id="outro-and-links">Outro and links</h2>
<p>Thank you all so much for reading this far. I hope something in here is helpful in your automation journey. Please explore and plug tools into different areas to see if you can make it work. Massive shout-out to Ryan Merolle; he deserves much credit for the work on this repository. Thank you so much for contributing.</p>
<ul>
<li><a href="https://unsplash.com/photos/lake-surrounded-by-pine-trees-near-snow-covered-mountain-tpmAv6c33dE">Featured Image Atanas Malamov</a></li>
<li><a href="https://twitter.com/ryanmerolle">Ryan on Twitter/X</a>, give him a follow</li>
<li><a href="https://github.com/JulioPDX/ci-avd-clab">GitHub repository</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Building my first mechanical keyboard</title>
            <link>https://juliopdx.com/2023/04/29/building-my-first-mechanical-keyboard/</link>
            <pubDate>Sat, 29 Apr 2023 11:14:03 -0700</pubDate>
            
            <guid>https://juliopdx.com/2023/04/29/building-my-first-mechanical-keyboard/</guid>
            <description>Introduction I recently went down the rabbit hole that is mechanical keyboards and, even worse, attempted to build my own. This blog will show you some random dude&amp;rsquo;s journey to making his first mechanical keyboard and avoiding some of my mistakes. I hope you enjoy the blog, and thank you for visiting the site.
Why build vs. buy Why would you build a keyboard? I mean, you could buy one, right?</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I recently went down the rabbit hole that is mechanical keyboards and, even worse, attempted to build my own. This blog will show you some random dude&rsquo;s journey to making his first mechanical keyboard and avoiding some of my mistakes. I hope you enjoy the blog, and thank you for visiting the site.</p>
<h2 id="why-build-vs-buy">Why build vs. buy</h2>
<p>Why would you build a keyboard? I mean, you could buy one, right? That is correct, and there&rsquo;s nothing wrong with buying a keyboard or mechanical keyboard pre-assembled. In fact, two other keyboards I own came pre-assembled. One is the <a href="https://shop.keyboard.io/products/keyboardio-atreus">Keyboardio Atreus</a> (still use this for travel) and the <a href="https://keeb.io/collections/iris-split-ergonomic-keyboard">Iris</a> keyboard I purchased from Keebio. Both are excellent, and I still use them today.</p>
<p>Even though I purchased two previous sets, I wondered if I could build one myself. I compare it to the same journey of putting a puzzle together, a Lego set, or a Gundam kit. Making something and seeing it come together brings a vast amount of joy.</p>
<h2 id="why-ortholinear">Why Ortholinear</h2>
<p>If you clicked on the previous links for the two keyboards mentioned above, you might wonder, &ldquo;Why do those keyboards look so weird?&rdquo; Fair question. These keyboards use an ortholinear layout where all the keys are the same size and have a uniform grid vs. a staggered layout found in traditional keyboards. The thought being this replicates the natural movement of our fingers. However, from my reading, there isn&rsquo;t any evidence that this improves the typing experience or leads to fewer wrist injuries.</p>
<h2 id="why-split">Why split</h2>
<p>If you click on the Iris keyboard link, you may notice that it&rsquo;s split into two pieces. The primary benefit here, since we have two unique pieces, we can rotate them to align with the natural resting position of our wrists vs. angling our wrists inward to align with a traditional staggered layout. The video below goes over all of this and more in excellent detail.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/unMXQTSQEak" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<h2 id="picking-the-right-keyboard">Picking the right keyboard</h2>
<p>I was now set on a split keyboard with an ortholinear layout. This is where it gets fun or tricky. There are many options for mechanical keyboards, and any portion of the build can be customized to fit your needs. The layout, materials, key switches, keycaps, and colors for everything. It can get overwhelming very fast.</p>
<p>I had some general points to follow to make the selection process manageable. This is in no particular order.</p>
<ol>
<li>Wood case or at least wood-filled filament option</li>
<li>Tactile switches</li>
<li>Split layout</li>
<li>Compact</li>
</ol>
<p>I don&rsquo;t remember how I found my final choice, but I&rsquo;m happy I did. Have you ever found a thing that makes you say, &ldquo;This fits me.&rdquo; When I saw the Minidox for the first time, I knew that was the keyboard I wanted to build. The Minidox is a more compact version of the popular <a href="https://ergodox-ez.com/">ErgoDox</a>. The PCB for the Minidox was created by <a href="https://github.com/That-Canadian/MiniDox_PCB">That-Canadian</a>, and I can&rsquo;t thank them enough for making this free and open source for the community.</p>
<h2 id="assembling-the-required-tools">Assembling the required tools</h2>
<p>I&rsquo;ll provide links to the items I used but feel free to source what works for you. You may have some items that I didn&rsquo;t have when I started. I&rsquo;ll include some optional pieces as well.</p>
<ol>
<li><a href="https://www.amazon.com/dp/B014Q44GZU?ref=ppx_yo2ov_dt_b_product_details&amp;th=1">Soldering iron kit</a> (Highly recommend)</li>
<li>PCB and electronics from <a href="https://shop.profetkeyboards.com/product/minidox-pcb-set">Profet Keyboards</a></li>
<li>Any switch you like, I went with <a href="https://a.co/d/0Q1eyX7">Glorious Pandas</a> pre-lubed</li>
<li>Any MX style switches you prefer</li>
<li>Case <a href="https://www.thingiverse.com/thing:4350283">STL files</a>, printed with <a href="https://www.treatstock.com/">treatstock</a></li>
<li><a href="https://www.amazon.com/dp/B07CMQ1SQH?ref=ppx_yo2ov_dt_b_product_details&amp;th=1">M3 screws</a> for the case</li>
<li><a href="https://www.amazon.com/dp/B019EHMN68?ref=ppx_yo2ov_dt_b_product_details&amp;th=1">TRRS cable</a></li>
</ol>
<p>The parts above are what I would consider required. Since I was going down this route, I wanted to see if I could improve the sound of the keyboard as much as possible. The items below are optional.</p>
<ol>
<li>Replacement <a href="https://www.amazon.com/gp/product/B087CLZ6RY/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&amp;th=1">USB-C Pro Micro</a> for the main side (left). The right side continues to use the micro USB that comes from Profet Keyboards.</li>
<li><a href="https://www.amazon.com/dp/B0BWXLMX4F?ref=ppx_yo2ov_dt_b_product_details&amp;th=1">Keyboard PE switch pads</a> for sound dampening.</li>
<li><a href="https://www.amazon.com/dp/B07WTPBQSW?ref=ppx_yo2ov_dt_b_product_details&amp;th=1">Neoprene roll</a> for more sound dampening and to fill in the case.</li>
<li><a href="https://a.co/d/dIVbiR2">Something to practice soldering</a>.</li>
</ol>
<h2 id="practice-practice-and-more-practice">Practice, practice, and more practice</h2>
<p>The soldering practice item above is optional, but it helped me tremendously. I last soldered eight years ago, and for many, this may be your first time looking at or handling a soldering iron. If I was going to do this, I didn&rsquo;t want the keyboard PCB to be what I practice on. If you like to live dangerously, then go right ahead.</p>
<h2 id="getting-started">Getting started</h2>
<p>I leveraged the build guide by <a href="https://imgur.com/a/vImo6">That-Canadian</a> to complete this board. With everything in hand, I started soldering all the main components; diodes, TRRS jacks, and controllers. The image below is one of the few I captured in this build. Take note of how small those diodes are. If you have trouble focusing on small items, I recommend getting a desk magnifying glass or headset unit. The build guide mentions that the diodes can go on the top or the bottom of the PCB. I recommend you install them on the bottom. In case something doesn&rsquo;t work right or you need to replace a non-working diode.</p>
<p><img src="/blog/images/minidox-soder.jpg" alt="Soder prep"></p>
<p><img src="/blog/images/minidox-diodes.jpg" alt="Diodes done"></p>
<h2 id="flashing-qmk-firmware">Flashing QMK firmware</h2>
<p>Now that all of the diodes are in. This is a great time to flash the controllers before mounting them on the PCB. This build uses <a href="https://github.com/qmk/qmk_firmware">QMK</a> to build and flash the firmware on the controllers. There&rsquo;s a GUI interface for folks on Windows or Mac. I use a Linux machine as my daily driver and went with the command-line route.</p>
<p>Set a Python virtual environment to install dependencies.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">python3 -m venv venv
source venv/bin/activate
pip3 install qmk
qmk setup
</code></pre></div><p>I leveraged all the default options during the setup stage. Although you may need to install additional dependencies on Linux, QMK should warn you if there&rsquo;s a missing dependency.</p>
<p>You can test whether QMK is ready by running the <code>qmk doctor</code> command. You can also attempt to compile a keymap.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">qmk compile -kb maple_computing/minidox/rev1 -km default
</code></pre></div><p>This is all great, but here&rsquo;s something that took me a little while to figure out. By default, QMK will try to flash a controller compatible with caterina, at least for the Minidox. Please read the documentation of your controller to see what bootloader is available. The USB C Pro Micro I linked earlier runs the DFU bootloader. This required one change in the keyboard&rsquo;s <code>rules.mk</code> file.</p>
<p>That sounds great but I had no idea where that file lived. Again, I&rsquo;m just a new user of all this tooling and all the documentation seems to assume you know where this lives. If you followed the default install steps for QMK, this will clone down the QMK repository to your main directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">cat qmk_firmware/keyboards/maple_computing/minidox/rules.mk
<span style="color:#75715e"># Build Options</span>
<span style="color:#75715e">#   change yes to no to disable</span>
<span style="color:#75715e">#</span>
BOOTMAGIC_ENABLE <span style="color:#f92672">=</span> no       <span style="color:#75715e"># Enable Bootmagic Lite</span>
MOUSEKEY_ENABLE <span style="color:#f92672">=</span> yes       <span style="color:#75715e"># Mouse keys</span>
EXTRAKEY_ENABLE <span style="color:#f92672">=</span> yes       <span style="color:#75715e"># Audio control and System control</span>
CONSOLE_ENABLE <span style="color:#f92672">=</span> no         <span style="color:#75715e"># Console for debug</span>
COMMAND_ENABLE <span style="color:#f92672">=</span> yes        <span style="color:#75715e"># Commands for debug and configuration</span>
NKRO_ENABLE <span style="color:#f92672">=</span> no            <span style="color:#75715e"># Enable N-Key Rollover</span>
BACKLIGHT_ENABLE <span style="color:#f92672">=</span> no       <span style="color:#75715e"># Enable keyboard backlight functionality</span>
RGBLIGHT_ENABLE <span style="color:#f92672">=</span> no        <span style="color:#75715e"># Enable keyboard RGB underglow</span>
AUDIO_ENABLE <span style="color:#f92672">=</span> no           <span style="color:#75715e"># Audio output</span>

SPLIT_KEYBOARD <span style="color:#f92672">=</span> yes

DEFAULT_FOLDER <span style="color:#f92672">=</span> maple_computing/minidox/rev1
</code></pre></div><p>The required setting for this controller is to add <code>BOOTLOADER = atmel-dfu</code> to the <code>rules.mk</code> file.</p>
<p>Now if we follow the required steps, we should see a new option for the bootloader to try and flash.</p>
<ol>
<li>Compile your keymap of choice</li>
<li>run <code>qmk flash -kb maple_computing/minidox/rev1 -km default</code></li>
<li>Plug in the controller USB to your PC</li>
<li>Either hit the reset button if installed or use tweezers on the reset and ground pins.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">qmk flash -kb maple_computing/minidox/rev1 -km default
Ψ Compiling keymap with gmake --jobs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> maple_computing/minidox/rev1:default:flash


QMK Firmware 0.20.5
Making maple_computing/minidox/rev1 with keymap default and target flash

avr-gcc <span style="color:#f92672">(</span>GCC<span style="color:#f92672">)</span> 5.4.0
Copyright <span style="color:#f92672">(</span>C<span style="color:#f92672">)</span> <span style="color:#ae81ff">2015</span> Free Software Foundation, Inc.
This is free software; see the source <span style="color:#66d9ef">for</span> copying conditions.  There is NO
warranty; not even <span style="color:#66d9ef">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Size before:
   text    data     bss     dec     hex filename
      <span style="color:#ae81ff">0</span>   <span style="color:#ae81ff">19794</span>       <span style="color:#ae81ff">0</span>   <span style="color:#ae81ff">19794</span>    4d52 maple_computing_minidox_rev1_default.hex

Compiling: quantum/command.c                                                                        <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
Linking: .build/maple_computing_minidox_rev1_default.elf                                            <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
Creating load file <span style="color:#66d9ef">for</span> flashing: .build/maple_computing_minidox_rev1_default.hex                    <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
Copying maple_computing_minidox_rev1_default.hex to qmk_firmware folder                             <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
Checking file size of maple_computing_minidox_rev1_default.hex                                      <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
 * The firmware size is fine - 19794/28672 <span style="color:#f92672">(</span>69%, <span style="color:#ae81ff">8878</span> bytes free<span style="color:#f92672">)</span>
Flashing <span style="color:#66d9ef">for</span> bootloader: atmel-dfu
Bootloader not found. Make sure the board is in bootloader mode. See https://docs.qmk.fm/#/newbs_flashing
 Trying again every 0.5s <span style="color:#f92672">(</span>Ctrl+C to cancel<span style="color:#f92672">)</span>...
</code></pre></div><p>Ultimately, I settled on a keymap variation of the one posted by <a href="https://github.com/fernando-jascovich/qmk_firmware/tree/fernando-jascovich_minidox_layout/keyboards/minidox/keymaps/khitsule">khitsule</a>.</p>
<p><img src="/blog/images/minidox.png" alt="Layout"></p>
<p>The color coding references what the keys will do depending on whether we hold the lower or raise key down.</p>
<h2 id="why-isnt-this-working">Why isn&rsquo;t this working</h2>
<p>At this point, everything is flashed and ready to go. I even have some switches installed on the case and the controller. I have enough connected to connect the two boards and see if all the keys work. The dream is that everything works on the first try, and I won&rsquo;t have to desolder anything. I was so wrong. Whenever I connected the two boards with the TRRS jacks, both boards would shut off. Long story short, after a lot of troubleshooting and desoldering the right side PCB not once but TWICE!!! The error could have been avoided if I just read the manual.</p>
<p><img src="/blog/images/minidox-pcb.jpg" alt="PCB"></p>
<p>Suppose you decide to build your own mechanical keyboard. In that case, I am sending you all the good vibes that you never have to remove the Pro Micro, at least not for troubleshooting reasons. Isn&rsquo;t this the ugliest thing you&rsquo;ve ever seen?</p>
<p><img src="/blog/images/minidox-removal.jpg" alt="Fail"></p>
<h2 id="quick-note-on-cases">Quick note on cases</h2>
<p>My father-in-law was kind enough to spare his time and material to print the case for my first build. That is very awesome of him. The case was linked earlier, but shout out to designers willing to share your awesome designs with the community.</p>
<p>Minor spoiler, but in case you don&rsquo;t have access to a 3D printer, you can use Treat Stock to order 3D prints. I don&rsquo;t have any affiliate links or sponsors. I used the service and think it&rsquo;s a neat option. Treat Stock gives you access to a vast list of printers. Think of it like Etsy for 3D prints. This allows you to select whatever color and materials are available.</p>
<p><img src="/blog/images/minidox-case1.jpg" alt="Case 1"></p>
<p><img src="/blog/images/minidox-case2.jpg" alt="Case 2"></p>
<h2 id="read-the-fu-manual">Read the FU***** manual</h2>
<p>Back to my reading the manual thought. After reading the build guide and soldering 30 diodes, I got too confident. So much so that I looked at the build guide but didn&rsquo;t read the not-so-fine print but rather the large print. The right-side controller has to be installed upside down because these PCBs are reversible. I can only blame myself. Just look at this screengrab from the actual build guide.</p>
<p><img src="/blog/images/pcb-check.png" alt="Case 2"></p>
<p>When I was desoldering the Pro Micro not once but TWICE, I seriously thought about quitting on this project. At this point it was getting spendy and time consuming. A veteran of building keyboards could probably put this together in under an hour. I was easily over two weekends on this. I pushed through, desoldered the Pro Micro, and soldered everything back into place.</p>
<h2 id="the-sweet-feeling-of-success">The sweet feeling of success</h2>
<p>Once everything was back in place, it was time to test again. Although the step back at the time seemed insurmountable, it was now just a funny memory of the past. Seeing it all work is a beautiful thing.</p>
<p><img src="/blog/images/minidox-test.jpg" alt="Case 2"></p>
<p><img src="/blog/images/minidox-complete.jpg" alt="Complete"></p>
<h2 id="would-i-do-it-again">Would I do it again</h2>
<p>Oddly enough, I had so much fun with the build that I completed the second one in a day or so, this time with no Pro Micro issues!</p>
<p>I used the Keyboard PE switch pads only on the second build. I cut out three neoprene foam layers to fill the case and improve the sound. If anyone is curious about a sound test, please let me know. I can always update the blog with a small audio clip.</p>
<p><img src="/blog/images/minidox-pe.jpg" alt="PE"></p>
<p>Pro Micro is installed correctly on the right side :). I used Azure dragon V2 switches on the second build.</p>
<p><img src="/blog/images/minidox-pro.jpg" alt="PE"></p>
<p>The case for the second build was ordered through Treat Stock in bronze PLA.</p>
<p><img src="/blog/images/minidox-2.jpg" alt="PE"></p>
<h2 id="outro">Outro</h2>
<p>Thank you all for reading this far. It really means a lot. Stepping into the world of mechanical keyboards has been fun. I&rsquo;ll make at least one more Minidox in the future, and after that, who knows. So let me know what you think, and stay safe out there.</p>
<ul>
<li>Featured image by <a href="https://unsplash.com/photos/8cVbbGCllOI">Kirk Lai</a></li>
<li><a href="https://aposymbiont.github.io/split-keyboards/">List of split keyboards</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>RPKI in the Lab</title>
            <link>https://juliopdx.com/2023/01/02/rpki-in-the-lab/</link>
            <pubDate>Mon, 02 Jan 2023 14:18:17 -0800</pubDate>
            
            <guid>https://juliopdx.com/2023/01/02/rpki-in-the-lab/</guid>
            <description>Introduction I recently finished the last chapters of Optimal Routing Design (great read). One of the chapters covered routing protocol security with protocols such as BGP, EIGRP, and OSPF. This echoed something in my head regarding Resource Public Key Infrastructure (RPKI). I knew the book&amp;rsquo;s printing was around 2003 and figured RPKI wouldn&amp;rsquo;t be in the book. This led me to wonder if I could get RPKI running in the lab, and the following blog post details some of that journey.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I recently finished the last chapters of Optimal Routing Design (great read). One of the chapters covered routing protocol security with protocols such as BGP, EIGRP, and OSPF. This echoed something in my head regarding Resource Public Key Infrastructure (RPKI). I knew the book&rsquo;s printing was around 2003 and figured RPKI wouldn&rsquo;t be in the book. This led me to wonder if I could get RPKI running in the lab, and the following blog post details some of that journey.</p>
<p>I knew very little about RPKI when I started down this road (arguably still know very little). However, I knew it was essential to set up to protect networks from the fate of route hijacks (someone advertising your network space without permission by accident or on purpose).</p>
<p>By default, peering over BGP isn&rsquo;t secure. The workflow is usually Autonomous systems (AS) peering together and start advertising networks. There&rsquo;s a default trust put in place that each peer will advertise the correct routes. Over time, we&rsquo;ve developed filtering or route policies to ensure we receive and advertise the proper routes. However, time has shown that these options alone aren&rsquo;t enough to prevent route hijacking or leaks.</p>
<h2 id="rpki-overview">RPKI overview</h2>
<p>RPKI provides a way to connect an AS number to IP addresses in its simplest form. The beauty is that this pairing is associated with something we trust. For example, this could be one of the regional internet registries (RIR).</p>
<div class="mermaid">
    
graph TD
    subgraph RIR
    AFRINIC
    ARIN
    APNIC
    LACNIC
    RIPENCC
    end
    IANA --> RIR
    AFRINIC --> LIR
    ARIN --> LIR
    APNIC --> LIR
    LACNIC --> LIR
    RIPENCC --> LIR
    LIR --> Customers

</div>
<p>The local internet registries (LIR) can create Route Origination Authorizations (ROA). The ROAs is the piece that states what AS can advertise a specific prefix. Once we have valid ROAs, we can determine if a route is valid, invalid, or unknown. You may wonder, &ldquo;okay, but where do these ROAs get stored?&rdquo; The ROAs are actually stored in multiple repositories. For example, the RIRs are the trusted source for RPKI information, including ROAs.</p>
<p>If I want to validate IP space given out by APNIC, then I&rsquo;ll subscribe to their repository and trust that the information is accurate and up-to-date. There is so much more involved in RPKI, but I&rsquo;d like to keep a blog short for once. I&rsquo;ll include some helpful links at the end if you want to learn more.</p>
<h2 id="getting-rpki-in-the-lab-with-routinator">Getting RPKI in the lab with Routinator</h2>
<p>In this lab, we&rsquo;ll leverage a local Validator of routes. This Validator can pull the latest ROA information from the defined repositories. The Validator we will use is NLnetLabs <a href="https://github.com/NLnetLabs/routinator">Routinator</a>. The Validator will use RPKI to Router (RTR) protocol to signal if a route is valid, invalid, or unknown. This has the benefit of reducing control plane impact on our routers. All the validation and checks are done by the Validator.</p>
<div class="mermaid">
    
graph TD
    R1(R1) ---|172.100.100.11|MGMT
    R2(R2) --- |172.100.100.12|MGMT
    RT(Routinator) --- |172.100.100.13|MGMT

</div>
<h3 id="containerlab-to-simplify-the-deployment">Containerlab to simplify the deployment</h3>
<p>NLnetLabs is very kind to include a container image of their Routinator software. Adding this to Containerlab is relatively easy. The example below is our lab file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">rpki</span>
<span style="color:#f92672">prefix</span>: <span style="color:#e6db74">&#34;&#34;</span>

<span style="color:#f92672">mgmt</span>:
  <span style="color:#f92672">network</span>: <span style="color:#ae81ff">statics</span>
  <span style="color:#f92672">ipv4_subnet</span>: <span style="color:#ae81ff">172.100.100.0</span><span style="color:#ae81ff">/24</span>

<span style="color:#f92672">topology</span>:
  <span style="color:#f92672">kinds</span>:
    <span style="color:#f92672">ceos</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.29.0.2F</span>
    <span style="color:#f92672">linux</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nlnetlabs/routinator</span>
  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">R1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.11</span>
      <span style="color:#f92672">startup-config</span>: <span style="color:#ae81ff">startup/R1.conf</span>
    <span style="color:#f92672">R2</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.12</span>
      <span style="color:#f92672">startup-config</span>: <span style="color:#ae81ff">startup/R2.conf</span>
    <span style="color:#f92672">RPKI</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.13</span>
      <span style="color:#f92672">ports</span>:
      - <span style="color:#ae81ff">80</span>:<span style="color:#ae81ff">8323</span>
  <span style="color:#f92672">links</span>:
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;R1:eth1&#34;</span>, <span style="color:#e6db74">&#34;R2:eth1&#34;</span>]
</code></pre></div><p>I kept the topology file as simple and small as possible. We will use two Arista EOS containers to form a BGP relationship and advertise routes. We will then implement RPKI to check the validity of those advertisements. The lab&rsquo;s startup configurations and GitHub repository will be linked at the end of this post.</p>
<h3 id="simple-bgp-config-and-advertising-routes">Simple BGP config and advertising routes</h3>
<p>We will simulate Google and Cloudflare as our two peering AS in our lab. We will advertise a network from each AS and another random network used for testing.</p>
<div class="mermaid">
    
flowchart LR
    subgraph AS 15169
    R1(R1)
    end
    subgraph AS 13335
    R2(R2)
    end
    R1 -->|192.168.100.1/32| R2
    R1 -->|8.8.8.0/24| R2
    R2 -->|192.168.100.2/32| R1
    R2 -->|1.1.1.0/24| R1
    linkStyle 0 stroke:green
    linkStyle 1 stroke:green
    linkStyle 2 stroke:green
    linkStyle 3 stroke:green

</div>
<p>Below is a simple BGP configuration from R1&rsquo;s perspective.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 15169
   router-id 192.168.100.1
   neighbor 172.16.0.2 remote-as 13335
   network 8.8.8.0/24
   network 192.168.100.1/32
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R1#show ip bgp | b Net
          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;      1.1.1.0/24             172.16.0.2            0       -          100     0       13335 i
 * &gt;      8.8.8.0/24             -                     -       -          -       0       i
 * &gt;      192.168.100.1/32       -                     -       -          -       0       i
 * &gt;      192.168.100.2/32       172.16.0.2            0       -          100     0       13335 i
R1#
</code></pre></div><p>Next, we&rsquo;ll add Routinator to our BGP configuration.</p>
<h3 id="connecting-to-routinator">Connecting to Routinator</h3>
<p>Setting up our environment with Routinator is relatively simple. Since all nodes in this topology share a management network, we&rsquo;ll use that in our RPKI configuration. Below is the configuration from R1&rsquo;s perspective.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 15169
   router-id 192.168.100.1
   neighbor 172.16.0.2 remote-as 13335
   network 8.8.8.0/24
   network 192.168.100.1/32
   !
   rpki cache routinator
      host 172.100.100.13 port 3323
      !
      transport tcp
   !
   rpki origin-validation
      ebgp local
</code></pre></div><p>Once this is implemented, we can check if our router is communicating with our Validator.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R1#show bgp rpki cache
routinator:
Host: 172.100.100.13 port 3323
VRF: default
Refresh interval: 326 seconds
Retry interval: 600 seconds
Expire interval: 7200 seconds
Preference: 5
Protocol version: 1
State: synced
Session ID: 23300
Serial number: 18
Last update sync: never
Last full sync: 0:00:10 ago
Last serial query: never
Last reset query: 0:00:41 ago
Entries: 390484
Connection: Active (0:00:41)
Transport information:
  Protocol: TCP
ROA tables: default


R1#
</code></pre></div><p>If we check our routes now, we will see two new codes.</p>
<ul>
<li><code>V</code>: Valid RPKI routes</li>
<li><code>U</code>: Unknown RPKI routes</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R1#show ip bgp | b Net
          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;    V 1.1.1.0/24             172.16.0.2            0       -          100     0       13335 i
 * &gt;      8.8.8.0/24             -                     -       -          -       0       i
 * &gt;      192.168.100.1/32       -                     -       -          -       0       i
 * &gt;    U 192.168.100.2/32       172.16.0.2            0       -          100     0       13335 i
R1#
</code></pre></div><h3 id="putting-policy-and-filtering-in-place">Putting policy and filtering in place</h3>
<p>At this point, we can see that our router is correctly communicating with Routinator and labeling routes as valid or unknown. However, you may notice that the routes are still in our routing table, even if they are unknown or invalid. In this case, we are simulating the <code>192.168.100.0</code> routes as bad. We can correct this by implementing some simple filtering.</p>
<div class="mermaid">
    
flowchart LR
    subgraph AS 135533
    R1(R1)
    end
    subgraph AS 135534
    R2(R2)
    end
    R1 --x|192.168.100.1/32| R2
    R1 -->|8.8.8.0/24| R2
    R2 --x|192.168.100.2/32| R1
    R2 -->|1.1.1.0/24| R1
    linkStyle 0 stroke:red
    linkStyle 1 stroke:green
    linkStyle 2 stroke:red
    linkStyle 3 stroke:green

</div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!
ip as-path access-list 1 permit ^$ any
!
route-map local-only permit 10
   description Only export local routes
   match as-path 1
!
route-map rpki-reject-non-valid deny 10
   description Reject non valid routes
   match origin-as validity invalid
!
route-map rpki-reject-non-valid deny 20
   description Reject not found routes
   match origin-as validity not-found
!
route-map rpki-reject-non-valid permit 100
!
router bgp 15169
   router-id 192.168.100.1
   neighbor 172.16.0.2 remote-as 13335
   neighbor 172.16.0.2 route-map rpki-reject-non-valid in
   neighbor 172.16.0.2 route-map local-only out
</code></pre></div><p>Below we can see the updates after filtering and compare that with all routes received from our neighbor.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R1#show ip bgp | b Net
          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;    V 1.1.1.0/24             172.16.0.2            0       -          100     0       13335 i
 * &gt;      8.8.8.0/24             -                     -       -          -       0       i
 * &gt;      192.168.100.1/32       -                     -       -          -       0       i
R1#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R1#show bgp neighbors 172.16.0.2 received-routes | b AS Path
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;    V 1.1.1.0/24             172.16.0.2            -       -          -       -       13335 i
        U 192.168.100.2/32       172.16.0.2            -       -          -       -       13335 i
R1#
</code></pre></div><p>If you&rsquo;d like, you can also check out the Routinator GUI at <code>http://&lt;Containerlab host&gt;</code>. Feel free to check out the number of RTR connections or the source for all the repositories.</p>
<h2 id="outro-and-links">Outro and links</h2>
<p>That&rsquo;s about it. I hope you learned a little about RPKI and got to play with it in the lab. Please check out the GitHub repository and modify it for other AS and network combos you may want to learn with. Happy new year, and I wish you all the best. Thank you for reading this far. It really means a lot!</p>
<ul>
<li><a href="https://lexica.art/">Featured image by lexica.art</a></li>
<li><a href="https://www.sanog.org/resources/sanog33/SANOG33_Tutorials-Routing-Security-Tutorial-RPKI_lab-Tashi-APNIC.pdf">APNIC lab used as inspiration</a></li>
<li><a href="https://aristanetworks.force.com/AristaCommunity/s/article/bgp-origin-validation-rpki">BGP Origin Validation with RPKI by Kenneth Finnegan</a></li>
<li><a href="https://github.com/NLnetLabs/routinator">Routinator GitHub</a></li>
<li><a href="https://blog.cloudflare.com/rpki/">RPKI - The required cryptographic upgrade to BGP routing by Martin J Levy</a></li>
<li><a href="https://www.rfc-editor.org/rfc/rfc6480#page-11">RFC 6480 - An Infrastructure to Support Secure Internet Routing</a></li>
<li><a href="https://en.wikipedia.org/wiki/Resource_Public_Key_Infrastructure">RPKI on Wikipedia</a></li>
<li><a href="https://github.com/JulioPDX/learning_labs/tree/main/labs/bgp/rpki">GitHub repository</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Static website with Pulumi, YAML, MkDocs, GCP, and CI/CD</title>
            <link>https://juliopdx.com/2022/11/06/static-website-with-pulumi-yaml-mkdocs-gcp-and-ci/cd/</link>
            <pubDate>Sun, 06 Nov 2022 14:20:59 -0800</pubDate>
            
            <guid>https://juliopdx.com/2022/11/06/static-website-with-pulumi-yaml-mkdocs-gcp-and-ci/cd/</guid>
            <description>Introduction A few weeks ago, a co-worker asked the team if there was a way to convert a set of Markdown files produced from the AVD Ansible Collection to nice HTML. I then created a simple GitHub Repo that explored various ways to build HTML from Markdown. That repository is combined with the example we&amp;rsquo;ll walk through in this blog.
I then went down the rabbit hole of getting a static site deployed in the various Clouds.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>A few weeks ago, a co-worker asked the team if there was a way to convert a set of Markdown files produced from the <a href="https://avd.sh/en/stable/">AVD Ansible Collection</a> to nice HTML. I then created a simple GitHub Repo that explored various ways to build HTML from Markdown. That repository is combined with the example we&rsquo;ll walk through in this blog.</p>
<p>I then went down the rabbit hole of getting a static site deployed in the various Clouds. If you look through the old <a href="https://github.com/JulioPDX/static-hosting-example/actions/runs/3397059440/jobs/5648812068">commits</a>, you can see the site is also deployed in <a href="https://orange-forest-091db261e.2.azurestaticapps.net/index.html">Azure</a> using a static web app. By the time you read this, that link may or may not be active. Although, it&rsquo;s using the free tier, I&rsquo;ll probably leave it up.</p>
<p>I&rsquo;ve used GCP to take advantage of the private container registry to push up some images. I wanted to challenge myself with deploying the static site in GCP (and learn a bit about GCP along the way). So I decided to make it harder (or easier?) on myself and leverage <a href="https://www.pulumi.com/">Pulumi</a> to deploy the infrastructure..</p>
<h2 id="what-is-pulumi">What is Pulumi</h2>
<p>I&rsquo;ve written about Pulumi in the past on <a href="https://packetpushers.net/introduction-to-pulumi-for-infrastructure-as-code-deployments/">Packet Pushers</a> (read it!), so I won&rsquo;t go into deep detail about it. But, in summary, it allows us to deploy infrastructure to the cloud using various programming languages (TypeScript, Python, Go, YAML). So, for example, if the team is very comfortable working with Go or Python, we can maintain those skills and use them to provision our cloud infrastructure. If you want to follow along, install Pulumi locally and create an account for their <a href="https://app.pulumi.com/">backend</a> service.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">curl -fsSL https://get.pulumi.com | sh
pulumi login
</code></pre></div><h2 id="pulumi--yaml">Pulumi &amp; YAML</h2>
<p>Did you see YAML in that list earlier? What the heck!? <a href="https://www.pulumi.com/blog/pulumi-yaml/">Pulumi recently</a> (wow, I just checked, and it was back in May) announced the support for YAML as another option to deploy infrastructure. Once again, hating myself, I decided to use YAML as the language of choice for this example (I&rsquo;ve used Python in the past).</p>
<h2 id="static-sites">Static sites</h2>
<p>Wow, that was a long intro, and it&rsquo;s still going. The original question was, &ldquo;we have a bunch of Markdown, and we want this converted to HTML.&rdquo; I&rsquo;ve been using <a href="https://www.mkdocs.org/">MkDocs</a> heavily in the last few months, so that&rsquo;s what I&rsquo;ll use in this example. Please note there are a lot of static site generators (SSG) out there. For example, this blog is created with <a href="https://gohugo.io/">Hugo</a>, but others such as <a href="https://jekyllrb.com/">Jekyll</a>, <a href="https://www.gatsbyjs.com/docs/glossary/static-site-generator/">Gatsby</a>, etc. will work just fine.</p>
<h2 id="mkdocs">MkDocs</h2>
<p>MkDocs is so easy to use that it&rsquo;s really grown on me these last few months. If you want to follow along, either clone the repository linked at the end or start fresh. We can begin by creating a Python virtual environment and installing a few items.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">pre-commit
mkdocs
mkdocs-material
mkdocs-include-dir-to-nav
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
</code></pre></div><p>Pre-commit will be explained later on. The other installs will leverage MkDocs, the fantastic <a href="https://squidfunk.github.io/mkdocs-material/">Material</a> theme, and a simple MkDocs plugin for navigation (optional). Great, now that we have that installed, we can review our <code>mkdocs.yaml</code> file. This is used to configure the various settings for our static site.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">site_name</span>: <span style="color:#ae81ff">Fabric Docs</span>

<span style="color:#75715e"># Configuration</span>
<span style="color:#f92672">use_directory_urls</span>: <span style="color:#66d9ef">false</span>
<span style="color:#f92672">site_url</span>: <span style="color:#e6db74">&#34;&#34;</span>
<span style="color:#f92672">edit_uri</span>: <span style="color:#e6db74">&#34;&#34;</span>
<span style="color:#f92672">docs_dir</span>: <span style="color:#ae81ff">avd-project</span>
<span style="color:#f92672">theme</span>:
  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">material</span>
  <span style="color:#f92672">palette</span>:
    - <span style="color:#f92672">media</span>: <span style="color:#e6db74">&#34;(prefers-color-scheme: light)&#34;</span>
      <span style="color:#f92672">scheme</span>: <span style="color:#ae81ff">default</span>
      <span style="color:#f92672">toggle</span>:
        <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">material/weather-sunny</span>
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Switch to dark mode</span>
    - <span style="color:#f92672">media</span>: <span style="color:#e6db74">&#34;(prefers-color-scheme: dark)&#34;</span>
      <span style="color:#f92672">scheme</span>: <span style="color:#ae81ff">slate</span>
      <span style="color:#f92672">toggle</span>:
        <span style="color:#f92672">icon</span>: <span style="color:#ae81ff">material/weather-night</span>
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Switch to light mode</span>
<span style="color:#f92672">plugins</span>:
  - <span style="color:#f92672">include_dir_to_nav</span>:
  - <span style="color:#ae81ff">search</span>
<span style="color:#f92672">extra_css</span>:
  - <span style="color:#ae81ff">stylesheets/extra.css</span>
<span style="color:#f92672">extra_javascript</span>:
  - <span style="color:#ae81ff">https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js</span>
  - <span style="color:#ae81ff">stylesheets/tables.js</span>
<span style="color:#f92672">nav</span>:
  - <span style="color:#f92672">Home</span>: <span style="color:#ae81ff">README.md</span>
  - <span style="color:#ae81ff">documentation</span>
  - <span style="color:#ae81ff">reports</span>
</code></pre></div><ul>
<li><code>docs_dir</code>: Points to a directory where our documents are stored and will be referenced from (default is <code>docs</code>).</li>
<li><code>theme.name</code>: This tells MkDocs that we will be leveraging the material theme.</li>
<li><code>plugins</code>: Any additional plugins we would like enabled. If you did not install <code>mkdocs-include-dir-to-nav</code> that can be disabled. I am leveraging to point mkdocs to folders within <code>docs_dir</code> to build the navigation tree. Instead of specifying them one by one.</li>
<li><code>extra_css</code>: Not required, adds different styling from the default.</li>
<li><code>extra_javascript</code>: Again, not required, used here to sort generated tables.</li>
<li><code>nav</code>: This is used to build all the navigation for our site.</li>
</ul>
<p>Excellent, clear as mud. We can &ldquo;serve&rdquo; the documents to get a live view as we modify them.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx in static-hosting-example on main <span style="color:#f92672">[</span>✘!?<span style="color:#f92672">]</span> via dev via 🐍 took 6s
❯ mkdocs serve
INFO     -  Building documentation...
INFO     -  Cleaning site directory
INFO     -  Documentation built in 0.84 seconds
INFO     -  <span style="color:#f92672">[</span>00:00:50<span style="color:#f92672">]</span> Watching paths <span style="color:#66d9ef">for</span> changes: <span style="color:#e6db74">&#39;avd-project&#39;</span>, <span style="color:#e6db74">&#39;mkdocs.yml&#39;</span>
INFO     -  <span style="color:#f92672">[</span>00:00:50<span style="color:#f92672">]</span> Serving on http://127.0.0.1:8000/
</code></pre></div><p>If we open that in the browser, we will see something like the following.</p>
<p><img src="/blog/images/static-site-mkdocs.png" alt="mkdocs"></p>
<p>Great, we have a simple static site ready for deployment in the cloud. If you run <code>mkdocs build</code>, mkdocs will package your static site in a new folder called <code>site</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx in static-hosting-example on main <span style="color:#f92672">[</span>✘!?<span style="color:#f92672">]</span> via dev via 🐍 took 12m32s
❯ mkdocs build
INFO     -  Cleaning site directory
INFO     -  Building documentation to directory: /home/juliopdx/repos/static-hosting-example/site
INFO     -  Documentation built in 0.67 seconds

juliopdx in static-hosting-example on main <span style="color:#f92672">[</span>✘!?<span style="color:#f92672">]</span> via dev via 🐍
❯
</code></pre></div><h2 id="deploying-the-static-site-to-gcp">Deploying the static site to GCP</h2>
<p>On the first build of this site, I used the <a href="https://cloud.google.com/storage/docs/hosting-static-website">official documentation</a>. That was a great exercise and showed how all the various resources are connected. But this example will show the steps to deploy this with Pulumi and YAML. If you are following along, you will need to install the <a href="https://www.pulumi.com/docs/get-started/gcp/begin/">Google Cloud SDK</a> and authorize with a user account.</p>
<h2 id="pulumi">Pulumi</h2>
<p>If you are using this repository, you can edit a few values and run <code>pulumi up</code>. Create a directory and start a new Pulumi project if you are creating your own project.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mkdir quickstart <span style="color:#f92672">&amp;&amp;</span> cd quickstart <span style="color:#f92672">&amp;&amp;</span> pulumi new gcp-yaml
</code></pre></div><blockquote>
<p>Please note, you will need to create a GCP project and enable the Google Compute API in your project to create resources.</p>
</blockquote>
<h3 id="bucket-and-rule-for-the-public">Bucket and rule for the public</h3>
<p>This example will leverage a storage bucket to store our site files.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">static-hosting-example</span>
<span style="color:#f92672">description</span>: <span style="color:#ae81ff">Static hosting on GCP</span>
<span style="color:#f92672">runtime</span>: <span style="color:#ae81ff">yaml</span>
<span style="color:#f92672">resources</span>:
  <span style="color:#75715e"># Create storage bucket</span>
  <span style="color:#f92672">web-bucket</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:storage:Bucket</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">website</span>:
        <span style="color:#f92672">mainPageSuffix</span>: <span style="color:#ae81ff">index.html</span>
        <span style="color:#f92672">notFoundPage</span>: <span style="color:#ae81ff">404.</span><span style="color:#ae81ff">html</span>
      <span style="color:#f92672">location</span>: <span style="color:#ae81ff">US</span>
      <span style="color:#f92672">uniformBucketLevelAccess</span>: <span style="color:#66d9ef">true</span>
      <span style="color:#f92672">forceDestroy</span>: <span style="color:#66d9ef">true</span>

  <span style="color:#75715e"># Enable public access to view site</span>
  <span style="color:#f92672">web-bucket-binding</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:storage:BucketIAMBinding</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">bucket</span>: <span style="color:#ae81ff">${web-bucket.name}</span>
      <span style="color:#f92672">role</span>: <span style="color:#e6db74">&#34;roles/storage.objectViewer&#34;</span>
      <span style="color:#f92672">members</span>: [<span style="color:#e6db74">&#34;allUsers&#34;</span>]
</code></pre></div><p>At the root, we have basic information like name, description, and runtime (YAML). We then list out the individual resources as dictionaries. For example, the <code>web-bucket</code> is of type <code>gcp:storage:Bucket</code>. I thought this was interesting syntax. In a programming language, you may see this as <code>gcp.storage.Bucket</code>. It essentially maps to what function will be executed to create the particular resource.</p>
<p>We then fill in the <code>properties</code> key with our unique configuration. We could also add a <code>name</code> key to hardcode what name we want to give a resource. I left this to Pulumi to name from the top-level key. For example, <code>web-bucket</code> will actually be named something crazy like <code>web-bucket12345445</code>.</p>
<p>We specify two essential files for our storage bucket, one for our main page and another when something is not found. I added the <code>forceDestroy</code> property so that I could destroy the bucket while testing. I believe if something is in the bucket and <code>forceDestroy</code> is not set, it will not delete the bucket.</p>
<p>Apologies that this section is so long, but it&rsquo;s important to understand how Pulumi works. The second resource is just setting an access rule to make our files viewable to the public. But notice, to pass in what bucket this rule should be associated with, we use <code>${web-bucket.name}</code>. This is an available parameter from our storage bucket resource. You can run <code>pulumi up -y</code> to create the resources.</p>
<h3 id="load-balancer-and-ssl-certs">Load balancer and SSL certs</h3>
<p>The goal of building this was to make it secure. Users get weird when they browse a site with the famous &ldquo;Your connection is not private, invalid SSL cert&rdquo; error. The note below is directly from the guide.</p>
<blockquote>
<p>Cloud Storage doesn&rsquo;t support custom domains with HTTPS on its own, so you also need to set up an SSL certificate attached to an HTTPS load balancer to serve your website through HTTPS.</p>
</blockquote>
<p>Okay, that sounds super easy&hellip;</p>
<h4 id="load-balancer-and-ssl-certificate">Load balancer and SSL certificate</h4>
<p>We&rsquo;ll need a static IP for future assignment to our global forwarding rules (also used for our DNS records).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">static-hosting-example</span>
<span style="color:#f92672">description</span>: <span style="color:#ae81ff">Static hosting on GCP</span>
<span style="color:#f92672">runtime</span>: <span style="color:#ae81ff">yaml</span>
<span style="color:#f92672">resources</span>:
<span style="color:#75715e"># ...</span>
  <span style="color:#75715e"># Global address</span>
  <span style="color:#f92672">webglobaladdress</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:GlobalAddress</span>
</code></pre></div><p>I don&rsquo;t want to manage SSL certs, so I&rsquo;ll just let GCP handle that. I then list any domains I plan to leverage with this certificate.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># ...</span>
  <span style="color:#75715e"># GCP managed cert with list of domains</span>
  <span style="color:#f92672">managedcert</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:ManagedSslCertificate</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">managed</span>:
        <span style="color:#f92672">domains</span>:
        - <span style="color:#ae81ff">juliopdx.org</span>
        - <span style="color:#ae81ff">www.juliopdx.org</span>
</code></pre></div><p>The backend service is interesting. First, we enable CDN (Content Delivery Network) since we want users to get quick and close delivery of our content. Although I&rsquo;m guessing GCP gets you with the pricing here, I would plan ahead. I&rsquo;m currently leveraging the free account on GCP for this deployment, which will be short-lived. We then associate our <code>web-bucket</code> where the site will be stored.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#75715e"># backend service leveraging our storage bucket</span>
  <span style="color:#f92672">sitebackend</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:BackendBucket</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">description</span>: <span style="color:#ae81ff">Contains a sweet site</span>
      <span style="color:#f92672">bucketName</span>: <span style="color:#ae81ff">${web-bucket.name}</span>
      <span style="color:#f92672">enableCdn</span>: <span style="color:#66d9ef">true</span>
      <span style="color:#f92672">cdnPolicy</span>:
        <span style="color:#f92672">cacheMode</span>: <span style="color:#ae81ff">CACHE_ALL_STATIC</span>
        <span style="color:#f92672">clientTtl</span>: <span style="color:#ae81ff">300</span>
        <span style="color:#f92672">defaultTtl</span>: <span style="color:#ae81ff">300</span>
        <span style="color:#f92672">maxTtl</span>: <span style="color:#ae81ff">1800</span>
</code></pre></div><p>From my research, it seems like <code>URLmaps</code> can get really complex. URLMaps route requests to backend services (<code>sitebackend</code> seen above). We don&rsquo;t have any defined, so it will just use the default. The default will route all the things to our <code>sitebackend</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#75715e"># Basic URL map pointing to backend</span>
  <span style="color:#f92672">webservicepaths</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:URLMap</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">defaultService</span>: <span style="color:#ae81ff">${sitebackend.id}</span>
</code></pre></div><p>The HTTPS proxy ties everything together. At this point, we associate our URLMap and newly managed SSL certificate. Our global forwarding rule can use the proxy to route incoming HTTPS requests to our <code>webservicepaths</code> URLMap.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#75715e"># HTTP proxy leveraging managed cert</span>
  <span style="color:#f92672">httpsproxy</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:TargetHttpsProxy</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">urlMap</span>: <span style="color:#ae81ff">${webservicepaths.selfLink}</span>
      <span style="color:#f92672">sslCertificates</span>:
      - <span style="color:#ae81ff">${managedcert.id}</span>
</code></pre></div><p>We tie the <code>httpsproxy</code> and <code>webglobaladdres</code> with our global forwarding rule. The <code>loadBalancingScheme: EXTERNAL_MANAGED</code> makes this a fancy load balancer vs. the classic one. You will probably get more features with the new one.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#75715e"># Forwarding rule pointing to HTTPS proxy</span>
  <span style="color:#f92672">defaultglobalforwardingrule</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:GlobalForwardingRule</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">target</span>: <span style="color:#ae81ff">${httpsproxy.id}</span>
      <span style="color:#f92672">portRange</span>: <span style="color:#ae81ff">443</span>
      <span style="color:#f92672">loadBalancingScheme</span>: <span style="color:#ae81ff">EXTERNAL_MANAGED</span>
      <span style="color:#f92672">ipAddress</span>: <span style="color:#ae81ff">${webglobaladdress.id}</span>
</code></pre></div><p>You can run <code>pulumi up -y</code> to create all the resources. Please make sure to update the domain names for the certificate with your information. Below is an example from the GCP console and the Load Balancer we just created.</p>
<p><img src="/blog/images/static-site-gcp.png" alt="Load Balancer"></p>
<h4 id="http-to-https-redirect">HTTP to HTTPS redirect</h4>
<p>We&rsquo;re almost done with the deployment. One other piece is directing users to HTTPS when they try to hit our site on port 80 or HTTP. The build-out is similar to our initial load balancer without the certificate or SSL policy.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#75715e"># HTTP to HTTPS redirect</span>
  <span style="color:#f92672">redirecturlmap</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:URLMap</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">defaultUrlRedirect</span>:
        <span style="color:#f92672">httpsRedirect</span>: <span style="color:#66d9ef">true</span>
        <span style="color:#f92672">stripQuery</span>: <span style="color:#66d9ef">false</span>

  <span style="color:#f92672">redirecttargethttpproxy</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:TargetHttpProxy</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">urlMap</span>: <span style="color:#ae81ff">${redirecturlmap.id}</span>

  <span style="color:#f92672">redirectglobalforwardingrule</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:GlobalForwardingRule</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">loadBalancingScheme</span>: <span style="color:#ae81ff">EXTERNAL_MANAGED</span>
      <span style="color:#f92672">target</span>: <span style="color:#ae81ff">${redirecttargethttpproxy.id}</span>
      <span style="color:#f92672">portRange</span>: <span style="color:#ae81ff">80</span>
      <span style="color:#f92672">ipAddress</span>: <span style="color:#ae81ff">${webglobaladdress.id}</span>
</code></pre></div><p>This diagram does a great job of summing up this workflow and our initial load balancer.</p>
<p><img src="https://cloud.google.com/static/load-balancing/images/http-to-https-redirect.svg" alt="http-to-http-from-gcp"></p>
<blockquote>
<p>Note: Pulumi also has this neat feature called outputs. We can save the output and use it later on in other programs or within the command line. Example in a moment.</p>
</blockquote>
<h2 id="connecting-domain-to-load-balancer">Connecting domain to load balancer</h2>
<p>Now that everything is created, we can update our DNS records to point to our public address. We can either use the console or the command line. The load balancer image shown earlier also reveals the public address.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx in static-hosting-example on main <span style="color:#f92672">[</span>!⇡<span style="color:#f92672">]</span> via dev via 🐍 took 2s
❯ gcloud config set project pulumi-web
Updated property <span style="color:#f92672">[</span>core/project<span style="color:#f92672">]</span>.

juliopdx in static-hosting-example on main <span style="color:#f92672">[</span>!⇡<span style="color:#f92672">]</span> via dev via 🐍
❯ gcloud compute addresses list
NAME                      ADDRESS/RANGE  TYPE      PURPOSE  NETWORK  REGION  SUBNET  STATUS
webglobaladdress-e33548f  34.98.78.165   EXTERNAL                                    IN_USE
</code></pre></div><p>I wanted to use <code>www.juliopdx.org</code> and <code>juliopdx.org</code>. I created two &ldquo;A&rdquo; records that matched my setup.</p>
<p><img src="/blog/images/static-site-dns.png" alt="DNS"></p>
<h2 id="uploading-files-using-gsutil">Uploading files using gsutil</h2>
<p>I couldn&rsquo;t figure out how to send a folder using the Pulumi YAML provider. Please send me a message if you know how to do that. After some research, I found this great post by Mickael Vieira, where they use Hugo and GitHub actions to deploy a static site. This led me to <code>gsutil</code>. It&rsquo;s a Python application that lets you interact with GCP storage from the command line. If you already ran <code>mkdocs build</code>, you should have a site folder in your directory. The following command can be used to send the files from a particular directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">gsutil -m rsync -d -c -r site gs://&lt;some bucket name&gt;
</code></pre></div><p>From Mickael&rsquo;s blog:</p>
<ul>
<li><code>m</code> to perform parallel synchronization;</li>
<li><code>r</code> to synchronize the directories and subdirectories recursively;</li>
<li><code>c</code> to use files’ checksum instead of their modification time to avoid sending all files during each deployment;</li>
<li><code>d</code> to delete the files that are no longer necessary.</li>
</ul>
<p>We can use the console to find out what the name of our bucket is, or we can use a Pulumi Output. For example, the end of our <code>Pulumi.yaml</code> file has this line.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">outputs</span>:
    <span style="color:#75715e"># Export the DNS name of the bucket</span>
    <span style="color:#f92672">bucketName</span>: <span style="color:#ae81ff">${web-bucket.url}</span>
</code></pre></div><p>We can leverage this in our <code>gsutil</code> command.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">gsutil -m rsync -d -c -r site <span style="color:#66d9ef">$(</span>pulumi stack output bucketName<span style="color:#66d9ef">)</span>
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx in static-hosting-example on main <span style="color:#f92672">[</span>!⇡<span style="color:#f92672">]</span> via dev via 🐍
❯ gsutil -m rsync -d -c -r site <span style="color:#66d9ef">$(</span>pulumi stack output bucketName<span style="color:#66d9ef">)</span>
Building synchronization state...
Starting synchronization...
Copying file://site/sitemap.xml <span style="color:#f92672">[</span>Content-Type<span style="color:#f92672">=</span>application/xml<span style="color:#f92672">]</span>...
Copying file://site/sitemap.xml.gz <span style="color:#f92672">[</span>Content-Type<span style="color:#f92672">=</span>application/xml<span style="color:#f92672">]</span>...
- <span style="color:#f92672">[</span>2/2 files<span style="color:#f92672">][</span>  1.0 KiB/  1.0 KiB<span style="color:#f92672">]</span> 100% Done
Operation completed over <span style="color:#ae81ff">2</span> objects/1.0 KiB.
</code></pre></div><p>After some time for the certificate to be created and DNS to propagate, you should see something like this. It&rsquo;s beautiful.</p>
<p><img src="/blog/images/static-site-live.png" alt="Site"></p>
<h2 id="thats-great-and-all-but-lets-talk-cicd-with-documentation">Thats great and all, but lets talk CI/CD with documentation</h2>
<p>So far, we have built up the infrastructure and the site content with local development, but we can incorporate CI/CD into our documentation. Contributors would still create content locally, but that content would then get pushed up to our Git repository to be checked before going out to production. In the following sections, I&rsquo;ll explain some fantastic tools and examples of moving some of these steps to GitHub actions.</p>
<h3 id="pre-commit">pre-commit</h3>
<p>Pre-commit is pretty awesome. You can supply a series of hooks that can then be used to lint your various programming languages, fix common file issues (trailing whitespace), or in our case, fix our documentation written in Markdown. If you followed along, you should have pre-commit installed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">pip install pre-commit
</code></pre></div><p>Pre-commit relies on a <code>.pre-commit-config.yaml</code> file at the root of your project to know what linters or hooks to run.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># See https://pre-commit.com for more information</span>
<span style="color:#75715e"># See https://pre-commit.com/hooks.html for more hooks</span>
<span style="color:#f92672">repos</span>:
  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/pre-commit/pre-commit-hooks</span>
    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v2.4.0</span>
    <span style="color:#f92672">hooks</span>:
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">trailing-whitespace</span>
        <span style="color:#f92672">exclude</span>: <span style="color:#ae81ff">ansible_collections/arista/avd/molecule</span>
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">end-of-file-fixer</span>
        <span style="color:#f92672">exclude_types</span>: [<span style="color:#ae81ff">svg, json]</span>

</code></pre></div><p>You can extend this as much as you want or add more hooks related to your project. To get started, run the following command.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx in static-hosting-example on main <span style="color:#f92672">[</span>!⇡<span style="color:#f92672">]</span> via  dev via 🐍
❯ pre-commit install --install-hooks
pre-commit installed at .git/hooks/pre-commit
<span style="color:#f92672">[</span>INFO<span style="color:#f92672">]</span> Initializing environment <span style="color:#66d9ef">for</span> https://github.com/pre-commit/pre-commit-hooks.
<span style="color:#f92672">[</span>INFO<span style="color:#f92672">]</span> Installing environment <span style="color:#66d9ef">for</span> https://github.com/pre-commit/pre-commit-hooks.
<span style="color:#f92672">[</span>INFO<span style="color:#f92672">]</span> Once installed this environment will be reused.
<span style="color:#f92672">[</span>INFO<span style="color:#f92672">]</span> This may take a few minutes...
<span style="color:#f92672">[</span>INFO<span style="color:#f92672">]</span> Once installed this environment will be reused.
<span style="color:#f92672">[</span>INFO<span style="color:#f92672">]</span> This may take a few minutes...
</code></pre></div><p>We can then check our code base for any issues.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
</code></pre></div><p>Great, everything passed. What about with an error? I removed the extra new line in <code>Pulumi.yaml</code>, which we want.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Failed
- hook id: end-of-file-fixer
- exit code: <span style="color:#ae81ff">1</span>
- files were modified by this hook

Fixing Pulumi.yaml
</code></pre></div><p>Pre-commit is much more powerful than I&rsquo;m showing here, but it&rsquo;s good for demonstration.</p>
<h3 id="markdownlint">Markdownlint</h3>
<p>We can also add Markdownlint to our pre-commit workflow.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#75715e"># ...</span>
  - <span style="color:#f92672">repo</span>: <span style="color:#ae81ff">https://github.com/igorshubovych/markdownlint-cli</span>
    <span style="color:#f92672">rev</span>: <span style="color:#ae81ff">v0.32.1</span>
    <span style="color:#f92672">hooks</span>:
      - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">markdownlint</span>
        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Check for Linting errors on MarkDown files</span>
        <span style="color:#f92672">args</span>:
          - --<span style="color:#ae81ff">config=.github/.markdownlint.yaml</span>
          - --<span style="color:#ae81ff">ignore-path=.github/.markdownlintignore</span>
          - --<span style="color:#ae81ff">fix</span>
</code></pre></div><p>I will only go into some detail since this is already so long. First, I created two files in the <code>.github</code> directory. One that points to the rules we want to enable or disable. Check out the repo if you would like to leverage them. I then ignore specific directories for files that may be created by automation. This is similar to a <code>.gitignore</code> file but for Markdown files. We can then run pre-commit again.</p>
<p>I&rsquo;ll add an error by adding another H1 heading in the README file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-markdown" data-lang="markdown"># Static site example

Simple workflows to view and build static site stuff.

# Test heading

<span style="color:#75715e">## Getting started
</span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check <span style="color:#66d9ef">for</span> Linting errors on MarkDown files...............................Failed
- hook id: markdownlint
- exit code: <span style="color:#ae81ff">1</span>

README.md:5 MD025/single-title/single-h1 Multiple top-level headings in the same document <span style="color:#f92672">[</span>Context: <span style="color:#e6db74">&#34;# Test heading&#34;</span><span style="color:#f92672">]</span>
Usage: markdownlint <span style="color:#f92672">[</span>options<span style="color:#f92672">]</span> &lt;files|directories|globs&gt;
</code></pre></div><p>In the case of Markdownlint, it will try to fix some errors, but it cant assume everything. The heading is not automatically removed for us, but some mistakes will be corrected. I&rsquo;ll remove the duplicate H1 heading and run pre-commit again.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check <span style="color:#66d9ef">for</span> Linting errors on MarkDown files...............................Passed
</code></pre></div><p>For more inforamtion, check out <a href="https://github.com/DavidAnson/markdownlint">markdownlint</a> and <a href="https://github.com/igorshubovych/markdownlint-cli">markdown-cli</a>.</p>
<h3 id="vale-on-the-command-line">Vale on the command line</h3>
<p><a href="https://vale.sh/">Vale</a> is a command line tool that can be used to check prose (your writing style). It does this based on a series of rules. The user can create these rules, or you can leverage pre-made styles. For example, Googles developer style guide (<a href="https://github.com/errata-ai/Google">errata-ai version of the style guide</a>). To use Vale, you must install the <a href="https://vale.sh/docs/vale-cli/installation/">binary</a>.</p>
<p>I&rsquo;ve found the easiest way to get Vale running is through Homebrew.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">brew install vale
</code></pre></div><p>Like most things, we need to provide Vale a configuration. This is done by Vale reading the <code>.vale.ini</code> file at the root of our project.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ini" data-lang="ini"><span style="color:#a6e22e">StylesPath</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">.github/styles</span>
<span style="color:#a6e22e">MinAlertLevel</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">error # suggestion, warning or error</span>

<span style="color:#75715e"># Only Markdown; change to whatever you&#39;re using.</span>
<span style="color:#66d9ef">[*.{md}]</span>
<span style="color:#75715e"># List of styles to load.</span>
<span style="color:#a6e22e">BasedOnStyles</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">Google</span>

</code></pre></div><p>I have the Google style in my repository in that <code>StylesPath</code>. I then chose the alert level of <code>error</code>. Feel free to play with the alert levels and the Google style rules that you agree or don&rsquo;t agree with. My good friend, <a href="https://blog.danielteycheney.com/posts/blog-automation-part-three/">Daniel Teycheney</a>, has an excellent blog post on using Vale. Check it out for more details.</p>
<p>I&rsquo;ll run Vale against one file with no errors.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ vale README.md
✔ <span style="color:#ae81ff">0</span> errors, <span style="color:#ae81ff">0</span> warnings and <span style="color:#ae81ff">0</span> suggestions in <span style="color:#ae81ff">1</span> file.
</code></pre></div><p>Now I&rsquo;ll introduce one error: Google hates exclamation marks in headings. Maybe hate is a strong word.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-markdown" data-lang="markdown"># Static site example!

Simple workflows to view and build static site stuff.
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ vale README.md

 README.md
 1:21  error  Don<span style="color:#960050;background-color:#1e0010">&#39;</span>t use exclamation points    Google.Exclamation
              in text.

✖ <span style="color:#ae81ff">1</span> error, <span style="color:#ae81ff">0</span> warnings and <span style="color:#ae81ff">0</span> suggestions in <span style="color:#ae81ff">1</span> file.
</code></pre></div><p>I think that&rsquo;s slick. With tools like these, you can ensure a consistent style and quality of your blog, documentation, or whatever else you&rsquo;re building. But the fun doesn&rsquo;t stop there. We can take all the tools mentioned and move them to GitHub actions.</p>
<h3 id="github-actions">GitHub actions</h3>
<p>I have the following files in my <code>.github/workflows/</code> directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ tree .github/workflows
.github/workflows
├── pr-manage.yml
└── static-web-app.yml

<span style="color:#ae81ff">0</span> directories, <span style="color:#ae81ff">2</span> files
</code></pre></div><p><code>pr-manage.yml</code> will be used for any pull requests. This workflow can run pre-commit, Vale, and Pulumi preview.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---

<span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Doc testing&#34;</span>

<span style="color:#f92672">on</span>:
  <span style="color:#75715e"># For a pull_request event, only branches and tags on the base are evaluated.</span>
  <span style="color:#f92672">push</span>:
    <span style="color:#f92672">branches-ignore</span>:
      - <span style="color:#ae81ff">main  </span> <span style="color:#75715e"># excludes main</span>

<span style="color:#f92672">jobs</span>:
  <span style="color:#f92672">pre_commit</span>:
    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Run pre-commit validation hooks</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
    <span style="color:#f92672">steps</span>:
    - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v3</span>
    - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v3</span>
    - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">pre-commit/action@v3.0.0</span>

  <span style="color:#f92672">vale</span>:
    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">runner / vale</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>
      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">errata-ai/vale-action@reviewdog</span>
        <span style="color:#f92672">env</span>:
          <span style="color:#75715e"># Required, set by GitHub actions automatically:</span>
          <span style="color:#75715e"># https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret</span>
          <span style="color:#f92672">GITHUB_TOKEN</span>: <span style="color:#ae81ff">${{secrets.GITHUB_TOKEN}}</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">files</span>: <span style="color:#ae81ff">avd-project/</span>
          <span style="color:#f92672">reporter</span>: <span style="color:#ae81ff">github-check</span>

  <span style="color:#f92672">pulumi-gcp-preview</span>:
    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">runner / pulumi</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>
      <span style="color:#75715e">###### GCP connect ######</span>
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">gcp auth</span>
        <span style="color:#f92672">uses</span>: <span style="color:#e6db74">&#39;google-github-actions/auth@v0&#39;</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">credentials_json</span>: <span style="color:#e6db74">&#39;${{ secrets.GCP_CREDENTIALS }}&#39;</span>
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Set up Cloud SDK</span>
        <span style="color:#f92672">uses</span>: <span style="color:#e6db74">&#39;google-github-actions/setup-gcloud@v0&#39;</span>
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pulumi preview</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">pulumi/actions@v3</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">command</span>: <span style="color:#ae81ff">preview</span>
          <span style="color:#f92672">stack-name</span>: <span style="color:#ae81ff">dev</span>
        <span style="color:#f92672">env</span>:
          <span style="color:#f92672">PULUMI_ACCESS_TOKEN</span>: <span style="color:#ae81ff">${{ secrets.PULUMI_ACCESS_TOKEN }}</span>

</code></pre></div><p>A lot is going on, but two secrets must be set to run this workflow. First, you need to create a <a href="https://console.cloud.google.com/iam-admin/serviceaccounts?">service account</a> user in GCP and add a new key in the form of JSON. This can then be added to repository secrets. You will also need to create a token in the Pulumi backend. This is a great <a href="https://www.pulumi.com/docs/guides/continuous-delivery/github-actions/#configuring-your-secrets">guide</a>.</p>
<p><img src="/blog/images/static-site-secrets.png" alt="Secrets"></p>
<p>I&rsquo;ll create a branch and push this up with some description added to one of our resources.</p>
<p><img src="/blog/images/static-site-pr.png" alt="branch-preview"></p>
<p>Looking closely, you can see we have an update for the description on the <code>ManagedSslCertificate</code> resource.</p>
<h3 id="push-on-main">Push on main</h3>
<p>Now that it is working, I&rsquo;ll run a pull request and merge these changes to our main branch. Then, instead of a preview, Pulumi will perform the changes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">GCP &amp; Pulumi static web app</span>

<span style="color:#f92672">on</span>:
  <span style="color:#f92672">push</span>:
    <span style="color:#f92672">branches</span>:
      - <span style="color:#ae81ff">main</span>


<span style="color:#f92672">jobs</span>:
  <span style="color:#f92672">build_and_deploy_job</span>:
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build and Deploy Job</span>
    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">submodules</span>: <span style="color:#66d9ef">true</span>

      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v2</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">python-version</span>: <span style="color:#ae81ff">3.</span><span style="color:#ae81ff">x</span>

      - <span style="color:#f92672">run</span>: <span style="color:#ae81ff">pip install -r requirements.txt</span>
      - <span style="color:#f92672">run</span>: <span style="color:#ae81ff">mkdocs build</span>

      <span style="color:#75715e">###### GCP push portion ######</span>
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">auth</span>
        <span style="color:#f92672">uses</span>: <span style="color:#e6db74">&#39;google-github-actions/auth@v0&#39;</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">credentials_json</span>: <span style="color:#e6db74">&#39;${{ secrets.GCP_CREDENTIALS }}&#39;</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Set up Cloud SDK</span>
        <span style="color:#f92672">uses</span>: <span style="color:#e6db74">&#39;google-github-actions/setup-gcloud@v0&#39;</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Use gcloud CLI</span>
        <span style="color:#f92672">run</span>: <span style="color:#e6db74">&#39;gcloud info&#39;</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">deploy infrastructure pulumi</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">pulumi/actions@v3</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">command</span>: <span style="color:#ae81ff">up</span>
          <span style="color:#f92672">stack-name</span>: <span style="color:#ae81ff">dev</span>
        <span style="color:#f92672">env</span>:
          <span style="color:#f92672">PULUMI_ACCESS_TOKEN</span>: <span style="color:#ae81ff">${{ secrets.PULUMI_ACCESS_TOKEN }}</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Push updated files to bucket</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">gsutil -m rsync -d -c -r site $(pulumi stack output bucketName)</span>
</code></pre></div><p>We&rsquo;ll run through the MkDocs steps to build our site. We then login to GCP using our secrets and run <code>pulumi up</code>. Once the infrastructure is set, we run gsutil to update the files if required. It seems that at the time of this writing, the <code>set-ouput</code> is deprecated, so I can&rsquo;t show the difference in the GitHub actions workflow. Check out the <a href="https://github.com/JulioPDX/static-hosting-example/actions/runs/3407575785/jobs/5667352310">job run</a> and the <a href="https://github.com/pulumi/actions/issues/769">issue</a>. We can see the updates on the console.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#75715e"># GCP managed cert with list of domains</span>
  <span style="color:#f92672">managedcert</span>:
    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">gcp:compute:ManagedSslCertificate</span>
    <span style="color:#f92672">properties</span>:
      <span style="color:#f92672">description</span>: <span style="color:#ae81ff">Some sweet cert</span>
      <span style="color:#f92672">managed</span>:
        <span style="color:#f92672">domains</span>:
        - <span style="color:#ae81ff">juliopdx.org</span>
        - <span style="color:#ae81ff">www.juliopdx.org</span>
</code></pre></div><p><img src="/blog/images/static-site-cert.png" alt="Cert description"></p>
<h2 id="final-thoughts-and-links">Final thoughts and links</h2>
<p>Wow, that was a lot of information. I hope you enjoyed the read and hopefully learned something along the way. Thank you for reading this far. As always, it really means a lot. I provided links where I may not have been as clear. Please check them out; they are great resources. Like anything I write, what I show here is just a subset of what these tools can do. Best of luck and happy labbing and learning.</p>
<ul>
<li><a href="https://unsplash.com/photos/zepnJQycr4U">Featured image by Kristopher Roller</a></li>
<li><a href="https://cloud.google.com/storage/docs/hosting-static-website">Google guide for hosting a static site</a></li>
<li><a href="https://www.pulumi.com/">Pulumi</a></li>
<li><a href="https://www.mkdocs.org/">MkDocs</a></li>
<li><a href="https://squidfunk.github.io/mkdocs-material/">MkDocs Material theme</a></li>
<li><a href="https://pre-commit.com/">pre-commit</a></li>
<li><a href="https://vale.sh/">Vale</a></li>
<li><a href="https://github.com/DavidAnson/markdownlint">Markdownlint</a></li>
<li>Blogs for inspiration
<ul>
<li>Joel Rozen - <a href="https://medium.com/develop-everything/create-a-cloud-run-service-and-https-load-balancer-with-pulumi-3ba542e60367">Create a Cloud Run service and https load balancer with Pulumi</a></li>
<li>Michael Vieira - <a href="https://www.mickaelvieira.com/blog/2020/01/29/deploying-a-static-website-to-google-cloud-storage-with-github-actions.html">Deploying a Static Website to Google Cloud Storage With Github Actions</a></li>
</ul>
</li>
<li><a href="https://github.com/JulioPDX/static-hosting-example">GitHub repository</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Slidev</title>
            <link>https://juliopdx.com/2022/11/01/slidev/</link>
            <pubDate>Tue, 01 Nov 2022 21:38:29 -0700</pubDate>
            
            <guid>https://juliopdx.com/2022/11/01/slidev/</guid>
            <description>Introduction A while back, I was building a technical presentation. Like most presentations, this would include diagrams, code blocks, images, etc. So I did what any of us usually did and looked at something like Google Slides or Microsoft PowerPoint.
For the longest time, I wasn&amp;rsquo;t a fan of how code blocks looked in these presentations or the gymnastics performed when taking a screenshot of some highlighted code or opening another app to create an image that would then be added to the presentation.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>A while back, I was building a technical presentation. Like most presentations, this would include diagrams, code blocks, images, etc. So I did what any of us usually did and looked at something like Google Slides or Microsoft PowerPoint.</p>
<p>For the longest time, I wasn&rsquo;t a fan of how code blocks looked in these presentations or the gymnastics performed when taking a screenshot of some highlighted code or opening another app to create an image that would then be added to the presentation. After some searches on the internet, I was blessed with something incredible, Slidev.</p>
<h2 id="what-is-slidev">What is Slidev</h2>
<p>Slidev is maintained by Anthony Fu and aims to be &ldquo;Presentation Slides for Developers.&rdquo; I&rsquo;m guessing Anthony was tired of running through certain hoops and figured, &ldquo;Why not just make some presentation software that mimics what I do daily?&rdquo;</p>
<p>I&rsquo;ll walk through some examples and my favorite features in a sec. Slidev makes the presentation-building workflow essentially like presentations as code. When building slides, developers and engineers can take advantage of using Markdown, CSS, and HTML.</p>
<h2 id="getting-started">Getting started</h2>
<p>Getting started with Slidev is as simple as one command (requires Node.js &gt;= 14.0).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">npm init slidev@latest
</code></pre></div><p>You will then be prompted to select a project name, if you would like to install it now, and what agent to use for the install. Example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ npm init slidev@latest
npx: installed <span style="color:#ae81ff">22</span> in 1.688s

  ●■▲
  Slidev Creator  v0.36.10

✔ Project name: … example
  Scaffolding project in example ...
  Done.

✔ Install and start it now? … yes
✔ Choose the agent › npm
</code></pre></div><p>The slides should open in your web browser automatically. You can run it manually using <code>npm run dev</code> if they don&rsquo;t. The slides will then be viewable at <code>localhost:3030</code>. By default, the slides can be modified at <code>slides.md</code>. In this case, I&rsquo;ll delete the built-in ones made by default and run through some examples.</p>
<h2 id="favorite-features">Favorite features</h2>
<p>Slidev has so many features that breaking them all down would take a while. So instead, I&rsquo;ll run through some simple examples, but I highly encourage you to look at the official documentation (linked at the end).</p>
<h3 id="creating-in-markdown">Creating in Markdown</h3>
<p>The ability to create slides in Markdown format was huge for me. Learning the syntax for creating documentation (and slides) is nice and straightforward. Below is an example of the initial slide.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-markdown" data-lang="markdown">---

# Welcome to the world of tomorrow

These slides are sweet!

---
</code></pre></div><p><img src="/blog/images/slidev-welcome.png" alt="Welcome"></p>
<h3 id="themes">Themes</h3>
<p>The default themes in Slidev are pretty sweet, but you can also use <a href="https://sli.dev/themes/gallery.html">custom themes</a>. I&rsquo;m a fan of the <a href="https://github.com/moudev/slidev-theme-purplin">Purplin theme</a>, mainly for its support of a bottom bar. I can change the front matter at the top of the <code>slides.md</code> file and run <code>npm run dev</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#75715e"># try also &#39;default&#39; to start simple</span>
<span style="color:#f92672">theme</span>: <span style="color:#ae81ff">purplin</span>
<span style="color:#75715e"># random image from a curated Unsplash collection by Anthony</span>
<span style="color:#75715e"># like them? see https://unsplash.com/collections/94734566/slidev</span>
<span style="color:#f92672">background</span>: <span style="color:#ae81ff">https://source.unsplash.com/collection/94734566/1920x1080</span>
<span style="color:#75715e"># apply any windi css classes to the current slide</span>
<span style="color:#f92672">class</span>: <span style="color:#e6db74">&#39;text-center&#39;</span>
<span style="color:#75715e"># https://sli.dev/custom/highlighters.html</span>
<span style="color:#f92672">highlighter</span>: <span style="color:#ae81ff">prism</span>
<span style="color:#75715e"># show line numbers in code blocks</span>
<span style="color:#f92672">lineNumbers</span>: <span style="color:#66d9ef">false</span>
<span style="color:#75715e"># some information about the slides, markdown enabled</span>
<span style="color:#f92672">info</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">  ## Slidev Starter Template
</span><span style="color:#e6db74">  Presentation slides for developers.
</span><span style="color:#e6db74">
</span><span style="color:#e6db74">  Learn more at [Sli.dev](https://sli.dev)</span>  
<span style="color:#75715e"># persist drawings in exports and build</span>
<span style="color:#f92672">drawings</span>:
  <span style="color:#f92672">persist</span>: <span style="color:#66d9ef">false</span>
<span style="color:#75715e"># use UnoCSS</span>
<span style="color:#f92672">css</span>: <span style="color:#ae81ff">unocss</span>
---
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ npm run dev

&gt; example@ dev /home/juliopdx/Documents/slidev-example/example
&gt; slidev --open

? The theme <span style="color:#e6db74">&#34;slidev-theme-purplin&#34;</span> was not found in your project, <span style="color:#66d9ef">do</span> you want to install it now? › <span style="color:#f92672">(</span>Y/n<span style="color:#f92672">)</span>
</code></pre></div><p>You&rsquo;ll lose the sweet image in your initial slide at this point, but that&rsquo;s okay for now. Next, I&rsquo;ll add another slide and include the bottom bar component with some additional Slidev features.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-markdown" data-lang="markdown">---
layout: iframe-right

# the web page source
url: https://juliopdx.com
---

# Hello, world

<span style="color:#66d9ef">-</span> Some sweet blog from a random dude on the internet &lt;<span style="color:#f92672">mdi-terminal</span> /&gt;
<span style="color:#66d9ef">-</span> Thank you for stopping by &lt;<span style="color:#f92672">mdi-check-box</span> /&gt;

&lt;<span style="color:#f92672">BarBottom</span>  <span style="color:#a6e22e">title</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Slidev Example&#34;</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;JulioPDX&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:logo-github</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;@Julio_PDX&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:logo-twitter</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;juliopdx.com&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:globe</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
&lt;/<span style="color:#f92672">BarBottom</span>&gt;
---
</code></pre></div><p>There is a lot going on here but stick with me.</p>
<ul>
<li>At the top, we have front matter for each slide representing a particular <a href="https://sli.dev/builtin/layouts.html">layout</a> we may want.</li>
<li>In this case, I chose an <code>iframe-right</code> to display a live web page as we go through a presentation. Imagine presenting project documentation on the fly.</li>
<li>I added some random header of <code>Hello, world</code> and a list of information.</li>
<li>A little subtle, but we can also add emojis all over the place.</li>
<li>The bar bottom piece is almost a copy of the Purplin documentation. It allows you to present data at the bottom of each slide.</li>
</ul>
<p><img src="/blog/images/slidev-web.png" alt="Web"></p>
<h3 id="diagrams">Diagrams</h3>
<p>Slidev supports mermaid diagrams which I thought was awesome. Below you will see a rough example I was working on showing some roles in the <a href="https://avd.sh/en/stable/">Arista AVD Collection</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-markdown" data-lang="markdown">---
layout: center
---

# Ansible AVD Collection

```mermaid {scale: .58}
flowchart LR
    1(YAML);2(eos_designs_role);3(eos_cli_config_gen_role);4(eos_config_deploy_cvp_role);5(eos_validate_state_role);6(CSV/MD);7(YAML);8(EOS.CFG);9(YAML);10(CSV/MD);11(CloudVision)
    subgraph vars
      1
    end
    subgraph eos_designs
    2
    end
    subgraph eos_cli_config_gen
    3
    end
    subgraph eos_config_deploy_cvp
    4
    end
    subgraph eos_validate_state
    5
    end
    subgraph Documentation
      6
    end
    subgraph Structured-EOS-Config
      7
    end
    subgraph EOS-CLI-Config
      8
    end
    subgraph Ansible-Inventory
      9
    end
    subgraph Reports
      10
    end
    subgraph CloudVision
      11
    end
    subgraph Arista-EOS-Fabric
      S1(S1);S2(S2);L1(L1);L2(L2);L3(L3);L4(L4)
      S1 <span style="color:#960050;background-color:#1e0010">&amp;</span> S2 --- L1 <span style="color:#960050;background-color:#1e0010">&amp;</span> L2 <span style="color:#960050;background-color:#1e0010">&amp;</span> L3 <span style="color:#960050;background-color:#1e0010">&amp;</span> L4
    end
    vars --&gt; eos_designs
    eos_designs --&gt; Documentation
    eos_cli_config_gen --&gt; Documentation
    eos_designs &lt;<span style="color:#f92672">--</span>&gt; Structured-EOS-Config
    Structured-EOS-Config --&gt; eos_cli_config_gen
    Structured-EOS-Config --&gt; eos_validate_state
    eos_cli_config_gen --&gt; EOS-CLI-Config
    EOS-CLI-Config --&gt; eos_config_deploy_cvp
    Ansible-Inventory --&gt; eos_config_deploy_cvp
    eos_validate_state &lt;<span style="color:#f92672">--</span>&gt; Reports
    eos_validate_state &lt;<span style="color:#f92672">--</span>&gt; Arista-EOS-Fabric
    CloudVision &lt;<span style="color:#f92672">--</span>&gt; eos_config_deploy_cvp
    CloudVision &lt;<span style="color:#f92672">--</span>&gt; Arista-EOS-Fabric



    classDef arista_blue fill:#27569B,stroke:#333,stroke-width:2px;
    class 1,2,3,4,5,6,7,8,9,10,11 arista_blue
```

&lt;<span style="color:#f92672">BarBottom</span>  <span style="color:#a6e22e">title</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Slidev Example&#34;</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;JulioPDX&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:logo-github</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;@Julio_PDX&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:logo-twitter</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;juliopdx.com&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:globe</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
&lt;/<span style="color:#f92672">BarBottom</span>&gt;
---
</code></pre></div><p><img src="/blog/images/slidev-avd.png" alt="AVD"></p>
<h3 id="syntax-highlighting">Syntax highlighting</h3>
<p>I honestly cant pick a favorite feature, but syntax highlighting might be it. You can create code blocks like any markdown document, and the code will be highlighted for you. Slidev can also highlight specific lines as you go through a presentation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-markdown" data-lang="markdown">---

# Colors &lt;mdi-heart /&gt;

&lt;<span style="color:#f92672">br</span>&gt;
&lt;<span style="color:#f92672">br</span>&gt;

&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">grid</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;~ cols-3 gap-2&#34;</span> <span style="color:#a6e22e">m</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-t-3&#34;</span>&gt;

```yaml {all|3-5}
api: 1

group: com.company
artifact: LoremIpsum
version: 1.0-SNAPSHOT
type: txt
<span style="color:#e6db74">```
</span><span style="color:#e6db74"></span>
```json {all|8-13}
{
  &#34;water&#34;: [
    true,
    343168281.98631763,
    1364186132
  ],
  &#34;sky&#34;: true,
  &#34;strong&#34;: [
    {
      &#34;stopped&#34;: &#34;quickly&#34;,
      &#34;kitchen&#34;: &#34;knife&#34;,
      &#34;after&#34;: -506746570.6858001
    },
    &#34;began&#34;,
    true
  ]
}
<span style="color:#e6db74">```</span>

```python {all|4}
my_numbers = [7, 7, 7]
first = &#34;Julio&#34;
last = &#34;PDX&#34;
print(&#34;Hello, world&#34;)
```

&lt;/<span style="color:#f92672">div</span>&gt;

&lt;<span style="color:#f92672">BarBottom</span>  <span style="color:#a6e22e">title</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Slidev Example&#34;</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;JulioPDX&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:logo-github</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;@Julio_PDX&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:logo-twitter</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
  &lt;<span style="color:#f92672">Item</span> <span style="color:#a6e22e">text</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;juliopdx.com&#34;</span>&gt;
    &lt;<span style="color:#f92672">carbon:globe</span> /&gt;
  &lt;/<span style="color:#f92672">Item</span>&gt;
&lt;/<span style="color:#f92672">BarBottom</span>&gt;
---
</code></pre></div><p>Again, a lot going on, but please stick with me.</p>
<ul>
<li>We added a small amount of HTML within the slide to lower the code blocks. We then split the slide into three columns. We could also use the layout called <a href="https://sli.dev/builtin/layouts.html#two-cols"><code>two-cols</code></a> in Slidev. But that only gets us two.</li>
<li>We then added three code blocks, one for YAML, JSON, and Python.</li>
<li>We can then ask Slidev to highlight portions of the code block as we go through the presentation. So, for example, <code>{all|3-5}</code> means, initially highlight everything, then on the next slide press, highlight lines three through five.</li>
</ul>
<h4 id="start">Start</h4>
<p><img src="/blog/images/slidev-before.png" alt="Start"></p>
<h4 id="a-few-click-later">A few click later</h4>
<p><img src="/blog/images/slidev-after.png" alt="After"></p>
<h2 id="final-thoughts-and-links">Final thoughts and links</h2>
<p>Thank you for reading this far. It means a lot. I hope you learned a bit about Slidev and give it a shot. I&rsquo;ve had a blast working with it so far. I took a long blogging break but hope to ramp back up. Maybe slower than I was publishing before. I wish you all the best, and thanks again.</p>
<ul>
<li><a href="https://sli.dev/">Slidev Documentation</a></li>
<li><a href="https://sli.dev/new">Try it out in a playground</a></li>
<li><a href="https://github.com/moudev/slidev-theme-purplin">Purplin Theme</a></li>
<li><a href="https://unsplash.com/photos/ln5drpv_ImI">Featrured image by Vincentiu Solomon</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Node Connections With Netreplica and Containerlab</title>
            <link>https://juliopdx.com/2022/03/28/node-connections-with-netreplica-and-containerlab/</link>
            <pubDate>Mon, 28 Mar 2022 12:08:20 -0700</pubDate>
            
            <guid>https://juliopdx.com/2022/03/28/node-connections-with-netreplica-and-containerlab/</guid>
            <description>Introduction Thank you all for checking out another blog post. All the support for the blog has been tremendous and I can&amp;rsquo;t thank you all enough. Packet Pushers invited me to write on their platform and it&amp;rsquo;s been a pleasure working with Drew and team. If you would like to check those out, I will link them at the end of this post! Today we will focus on an interesting tool in the community, Netreplica, but specifically Graphite.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Thank you all for checking out another blog post. All the support for the blog has been tremendous and I can&rsquo;t thank you all enough. Packet Pushers invited me to write on their platform and it&rsquo;s been a pleasure working with Drew and team. If you would like to check those out, I will link them at the end of this post! Today we will focus on an interesting tool in the community, Netreplica, but specifically Graphite.</p>
<p>Some time ago Alex(<a href="https://twitter.com/Bortok">@bortok</a>) shared an example of using NextUI to build a visual from a Containerlab file. I asked if there was a way to click on the icon and just open an SSH session. This could then open a terminal like SecureCRT, web page, or some other means of interacting with a session.</p>
<p><img src="/blog/images/request-ssh.png" alt="Request"></p>
<h2 id="netreplica-graphite">Netreplica Graphite</h2>
<p>I&rsquo;ll walk through an example to get going and current pros and cons. Please note, Graphite is on initial phases of development. Please reach out to the folks at Netreplica(GitHub link below) for any feedback. There&rsquo;s a couple options to get going with Graphite but I found standing up a container within a Containerlab file was the most simple. In the example below we are running 4 containers. One is a standalone container(ssh) with no connections to the rest of the topology. The Graphite container is using a special tag with the webssh implementation.</p>
<p><code>simple.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">simple</span>
<span style="color:#f92672">prefix</span>: <span style="color:#e6db74">&#34;&#34;</span>

<span style="color:#f92672">mgmt</span>:
  <span style="color:#f92672">network</span>: <span style="color:#ae81ff">statics</span>
  <span style="color:#f92672">ipv4_subnet</span>: <span style="color:#ae81ff">172.100.100.0</span><span style="color:#ae81ff">/24</span>

<span style="color:#f92672">topology</span>:
  <span style="color:#f92672">kinds</span>:
    <span style="color:#f92672">srl</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/nokia/srlinux</span>
  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">pdx-rtr-01</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">srl</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.11</span>
    <span style="color:#f92672">pdx-rtr-02</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">srl</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.12</span>
    <span style="color:#f92672">pdx-rtr-03</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">srl</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.13</span>
    <span style="color:#f92672">ssh</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">netreplica/graphite:webssh2</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.100</span>
      <span style="color:#f92672">env</span>:
        <span style="color:#f92672">GRAPHITE_DEFAULT_TYPE</span>: <span style="color:#ae81ff">clab</span>
        <span style="color:#f92672">GRAPHITE_DEFAULT_TOPO</span>: <span style="color:#ae81ff">simple</span>
        <span style="color:#f92672">CLAB_SSH_CONNECTION</span>: <span style="color:#ae81ff">${SSH_CONNECTION}</span>
      <span style="color:#f92672">binds</span>:
        - <span style="color:#ae81ff">.:/htdocs/clab</span>
      <span style="color:#f92672">ports</span>:
        - <span style="color:#ae81ff">8080</span>:<span style="color:#ae81ff">80</span>
      <span style="color:#f92672">exec</span>:
        - <span style="color:#ae81ff">sh -c &#39;generate_offline_graph.sh&#39;</span>
        - <span style="color:#ae81ff">sh -c &#39;graphite_motd.sh 8080&#39;</span>
      <span style="color:#f92672">labels</span>:
        <span style="color:#f92672">graph-hide</span>: <span style="color:#66d9ef">yes</span>
  <span style="color:#f92672">links</span>:
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-01:e1-1&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-02:e1-1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-01:e1-2&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-03:e1-1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-02:e1-2&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-03:e1-2&#34;</span>]

</code></pre></div><p>There&rsquo;s a couple things to note:</p>
<ul>
<li>Static IP addresses for management required.</li>
<li>Topology file should be shorthand and use longer yaml syntax. For example <code>simple.clab.yml</code> will not work, but <code>simple.yaml</code> will.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ sudo -E containerlab deploy -t simple.yaml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Containerlab v0.25.1 started
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Parsing &amp; checking topology file: simple.yaml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating lab directory: /home/juliopdx/git/gcl/clab-simple
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating docker network: Name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;statics&#34;</span>, IPv4Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;172.100.100.0/24&#34;</span>, IPv6Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&#34;</span>, MTU<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;1500&#34;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: <span style="color:#e6db74">&#34;ssh&#34;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: <span style="color:#e6db74">&#34;pdx-rtr-03&#34;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: <span style="color:#e6db74">&#34;pdx-rtr-02&#34;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: <span style="color:#e6db74">&#34;pdx-rtr-01&#34;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating virtual wire: pdx-rtr-01:e1-1 &lt;--&gt; pdx-rtr-02:e1-1
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating virtual wire: pdx-rtr-01:e1-2 &lt;--&gt; pdx-rtr-03:e1-1
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating virtual wire: pdx-rtr-02:e1-2 &lt;--&gt; pdx-rtr-03:e1-2
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Nokia SR Linux <span style="color:#e6db74">&#39;pdx-rtr-01&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Nokia SR Linux <span style="color:#e6db74">&#39;pdx-rtr-02&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Nokia SR Linux <span style="color:#e6db74">&#39;pdx-rtr-03&#39;</span> node
INFO<span style="color:#f92672">[</span>0015<span style="color:#f92672">]</span> Adding containerlab host entries to /etc/hosts file
INFO<span style="color:#f92672">[</span>0015<span style="color:#f92672">]</span> Executed command <span style="color:#e6db74">&#39;sh -c &#39;</span>generate_offline_graph.sh<span style="color:#e6db74">&#39;&#39;</span> on ssh. stdout:
Visualizing topology: simple.yaml
--truncated--
+---+------------+--------------+-----------------------------+-------+---------+--------------------+--------------+
| <span style="color:#75715e"># |    Name    | Container ID |            Image            | Kind  |  State  |    IPv4 Address    | IPv6 Address |</span>
+---+------------+--------------+-----------------------------+-------+---------+--------------------+--------------+
| <span style="color:#ae81ff">1</span> | pdx-rtr-01 | 81cd5aa9e62e | ghcr.io/nokia/srlinux       | srl   | running | 172.100.100.11/24  | N/A          |
| <span style="color:#ae81ff">2</span> | pdx-rtr-02 | 29a158c32b37 | ghcr.io/nokia/srlinux       | srl   | running | 172.100.100.12/24  | N/A          |
| <span style="color:#ae81ff">3</span> | pdx-rtr-03 | 4fe7e0beac6f | ghcr.io/nokia/srlinux       | srl   | running | 172.100.100.13/24  | N/A          |
| <span style="color:#ae81ff">4</span> | ssh        | 3eb1e14ca34c | netreplica/graphite:webssh2 | linux | running | 172.100.100.100/24 | N/A          |
+---+------------+--------------+-----------------------------+-------+---------+--------------------+--------------+
</code></pre></div><p>Now that we&rsquo;ve deployed the topology, we can go to <code>localhost:8080/graphite/</code>. In my case I&rsquo;m running Containerlab on my local laptop. Depending on your Containerlab file, you should see something like below.</p>
<p><img src="/blog/images/replicate-demo.png" alt="Topology Sample"></p>
<p>Here&rsquo;s where it gets fun. Check out what happens when we click on one of the nodes.</p>
<p><img src="/blog/images/replicate-click.png" alt="Replicate Click"></p>
<p>Now if we click on that link within the node, you will get a new connection window. In this case, I get a new Chrome window to the selected device.</p>
<p><img src="/blog/images/replicate-window.png" alt="Replicate Window"></p>
<h2 id="final-thoughts-and-links">Final Thoughts and Links</h2>
<p>I&rsquo;m a big fan of these tools that are making the lives easier for end users. Whether getting started with containers, learning network technologies, or just lowering the barrier of entry. Check out Graphite if you get a chance and reach out to the team on GitHub if you have any feedback or issues. Thank you for reading this far and I wish you all the best!</p>
<ul>
<li><a href="https://github.com/netreplica/graphite">netreplica/graphite</a></li>
<li><a href="https://containerlab.dev/">Containerlab</a></li>
<li><a href="https://unsplash.com/photos/4Mw7nkQDByk">Featured Image by Gabriel Heinzer</a></li>
<li><a href="https://packetpushers.net/author/julio-perez/">Blogs on Packet Pushers!</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>ExaBGP in the Lab</title>
            <link>https://juliopdx.com/2022/02/25/exabgp-in-the-lab/</link>
            <pubDate>Fri, 25 Feb 2022 15:06:14 -0800</pubDate>
            
            <guid>https://juliopdx.com/2022/02/25/exabgp-in-the-lab/</guid>
            <description>Intro I was recently working my way through Optimal Routing design by Cisco Press&amp;hellip; Can you tell that I get through books very slowly? I&amp;rsquo;ve probably used that sentence in my blog posts three or four times now! Either way, this chapter focused on scaling BGP and dealing with external connections. The section that really got me was external connections. The books goes over a lot, for example, handling inbound traffic, outbound traffic, full routes vs partial vs default.</description>
            <content type="html"><![CDATA[<h2 id="intro">Intro</h2>
<p>I was recently working my way through Optimal Routing design by Cisco Press&hellip; Can you tell that I get through books very slowly? I&rsquo;ve probably used that sentence in my blog posts three or four times now! Either way, this chapter focused on scaling BGP and dealing with external connections. The section that really got me was external connections. The books goes over a lot, for example, handling inbound traffic, outbound traffic, full routes vs partial vs default. I wanted to see if there was a way to get a sample of the internet routing table in the lab. If you&rsquo;re reading this then the goal was accomplished or I failed miserably and I feel like venting to the world. Either way, if you skipped over this paragraph then you missed my bad humor. Shame on you!</p>
<h2 id="the-topology">The Topology</h2>
<div class="mermaid">
    
graph LR
  1((cmpa1))
  2((cmpa2))
  3((ispa))
  4((ispb))
  5((cloud))
  6((internet))
  1---3
  2---4
  3---5
  4---5
  6---5

</div>
<p>Company A(cmpa) has two connections to different ISPs. ISPA is in AS 65001 and ISPB is in AS 65002. Company A also has an iBGP peering between its edge nodes in cmpa1 and cmpa2. In this example I used Arista cEOS nodes as the company routers and Nokia SR Linux at the ISP side.</p>
<h2 id="working-with-exabgp">Working with ExaBGP</h2>
<p>When going down a particular route, I usually always check if something similar has been done before. I really don&rsquo;t like to reinvent the wheel and if someone has created it freely for the world, why not give it a shot! Thankfully I found two really great resources for ExaBGP. One from <a href="https://jasonmurray.org/posts/2020/exabgp-fulltable/">Jason Murray</a>, who made this happen before in GNS3 and a few great posts by <a href="https://thepacketgeek.com/exabgp/">Mat Wood</a>. These posts were incredibly insightful but the step to convert the BGP data from RIPE to an ExaBGP format was time consuming.</p>
<h3 id="converting-manual-steps-to-dockerfile">Converting Manual Steps to Dockerfile</h3>
<p>I eventually decided on wrapping these steps in a Dockerfile, which could then be used to build a container image. My eventual goal was to use this in a lab with something like Containerlab or Net-sim Tools.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-dockerfile" data-lang="dockerfile"><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> ubuntu:20.04</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Install dependencies</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> bgp.cfg .<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apt update<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apt install python3-pip net-tools wget mrtparse vim nano -y <span style="color:#f92672">&amp;&amp;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    rm -rf /var/lib/apt/lists/* <span style="color:#f92672">&amp;&amp;</span> apt clean<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> pip install exabgp<span style="color:#f92672">==</span>4.2.17<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Download latest table and remove when complete</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> wget https://data.ris.ripe.net/rrc16/latest-bview.gz<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> mrt2exabgp -G -P -4 172.16.2.1 latest-bview.gz &gt; fullbgptable.py<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> rm -rf latest-bview.gz<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span></code></pre></div><p>The default configuration file I created for ExaBGP supports two neighbors on AS 65001 and 65002 respectively. Vim and nano are included to allow users to modify this for their deployment. For example you could have ISP nodes on the same AS to simulate multiple external connections to the same ISP.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">process announce-routes {
    run python3 fullbgptable.py;
    encoder json;
}

template {
    neighbor AS_65000 {
        router-id 172.16.2.1;
        local-as 65000;
        local-address 172.16.2.1;
    }
}

neighbor 172.16.2.2 {
    inherit AS_65000;
    peer-as 65001;
}

neighbor 172.16.2.3 {
    inherit AS_65000;
    peer-as 65002;
}

</code></pre></div><p>This container is now on <a href="https://hub.docker.com/repository/docker/juliopdx/exabgp-irt">Docker Hub</a>, feel free to pull it down and check it out. After it was on Docker Hub, I had this random idea to automate the updates to the docker image. For example, the BGP view from RIPE is updated a decent amount throughout the day. I think it would be neat to grab the latest update at the end of the day and rebuild the docker image. This could then be tagged with <code>latest</code> and the <code>date</code> it was created.</p>
<h3 id="migrating-docker-build-to-github-actions">Migrating Docker Build to GitHub Actions</h3>
<p>GitHub Actions was fairly new to me, besides some hello world example. I found this neat example from <a href="https://medium.com/platformer-blog/lets-publish-a-docker-image-to-docker-hub-using-a-github-action-f0b17e5cceb3">Chamod Shehanka</a> that I used as a starting point. I still had to add scheduled updates and syncing the readme/description on Docker Hub with the repository on GitHub. The latter is only available for some pro version of Docker Hub automated builds but luckily there&rsquo;s a GitHub Action to get around this. Example starting workflow file below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">Docker Image CI</span>

<span style="color:#f92672">on</span>:
  <span style="color:#f92672">push</span>:
    <span style="color:#f92672">branches</span>: [ <span style="color:#ae81ff">main ]</span>
  <span style="color:#f92672">pull_request</span>:
    <span style="color:#f92672">branches</span>: [ <span style="color:#ae81ff">main ]</span>

<span style="color:#f92672">jobs</span>:

  <span style="color:#f92672">build</span>:

    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>

    <span style="color:#f92672">steps</span>:
    - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>
    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">docker login</span>
      <span style="color:#f92672">env</span>:
        <span style="color:#f92672">DOCKER_USER</span>: <span style="color:#ae81ff">${{secrets.DOCKER_USER }}</span>
        <span style="color:#f92672">DOCKER_PASSWORD</span>: <span style="color:#ae81ff">${{secrets.DOCKER_PASSWORD }}</span>
      <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">        </span>        <span style="color:#ae81ff">docker login -u $DOCKER_USER -p $DOCKER_PASSWORD</span>
    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build the Docker image</span>
      <span style="color:#f92672">run</span>: <span style="color:#ae81ff">docker build . --file Dockerfile --tag juliopdx/exabgp-irt:$(date +%s)</span>

    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Docker Push</span>
      <span style="color:#f92672">run</span>: <span style="color:#ae81ff">docker push juliopdx/exabgp-irt</span>

</code></pre></div><h4 id="adding-github-actions">Adding GitHub Actions</h4>
<p>Shorty after I discovered two really slick GitHub Actions from some awesome folks to streamline the workflow and arguably make it look a bit cleaner. One being <a href="https://github.com/marketplace/actions/docker-build-push-action">Docker Build &amp; Push by Sean Smith</a> and the other <a href="https://github.com/marketplace/actions/docker-hub-readme-description-sync">sync-readme by Damian Mee</a>. Both offer a simplicity to the workflow file that I really enjoy. This led me to fairly finalizing this docker workflow.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">Docker Image CI</span>

<span style="color:#f92672">on</span>:
  <span style="color:#f92672">push</span>:
    <span style="color:#f92672">branches</span>: [<span style="color:#ae81ff">main]</span>
  <span style="color:#f92672">pull_request</span>:
    <span style="color:#f92672">branches</span>: [<span style="color:#ae81ff">main]</span>
  <span style="color:#f92672">schedule</span>:
    - <span style="color:#f92672">cron</span>: <span style="color:#e6db74">&#34;0 0 * * *&#34;</span>

<span style="color:#f92672">jobs</span>:
  <span style="color:#f92672">build</span>:
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>

    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">check out code</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v2</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">build and push docker image</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">mr-smithers-excellent/docker-build-push@v5</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">juliopdx/exabgp-irt</span>
          <span style="color:#f92672">registry</span>: <span style="color:#ae81ff">docker.io</span>
          <span style="color:#f92672">username</span>: <span style="color:#ae81ff">${{ secrets.DOCKER_USER }}</span>
          <span style="color:#f92672">password</span>: <span style="color:#ae81ff">${{ secrets.DOCKER_PASSWORD }}</span>
          <span style="color:#f92672">tags</span>: <span style="color:#ae81ff">latest, $(date +%F)</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">sync desc and readme</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">meeDamian/sync-readme@v1.0.6</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">user</span>: <span style="color:#ae81ff">${{secrets.DOCKER_USER }}</span>
          <span style="color:#f92672">pass</span>: <span style="color:#ae81ff">${{ secrets.DOCKER_PASSWORD }}</span>

</code></pre></div><h2 id="incorporating-exabgp-in-the-lab">Incorporating ExaBGP in the Lab</h2>
<p>Now that we have the docker image created, we can incorporate it in our lab. For this example I leveraged Containerlab.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">irt</span>
<span style="color:#f92672">prefix</span>: <span style="color:#e6db74">&#34;&#34;</span>

<span style="color:#f92672">topology</span>:

  <span style="color:#f92672">kinds</span>:
    <span style="color:#f92672">ceos</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>
    <span style="color:#f92672">srl</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/nokia/srlinux</span>

  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">ispa</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">srl</span>
      <span style="color:#f92672">startup-config</span>: <span style="color:#ae81ff">conf/ispa.json</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.20.20.11</span>
    <span style="color:#f92672">ispb</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">srl</span>
      <span style="color:#f92672">startup-config</span>: <span style="color:#ae81ff">conf/ispb.json</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.20.20.12</span>
    <span style="color:#f92672">internet</span>: <span style="color:#75715e"># ifconfig eth1 172.16.2.1 netmask 255.255.255.0 up</span>
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">juliopdx/exabgp-irt</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.20.20.13</span>
    <span style="color:#f92672">cmpa1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">startup-config</span>: <span style="color:#ae81ff">conf/cmpa1.conf</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.20.20.14</span>
    <span style="color:#f92672">cmpa2</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">startup-config</span>: <span style="color:#ae81ff">conf/cmpa2.conf</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.20.20.15</span>
    <span style="color:#f92672">cloud</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.20.20.16</span>

  <span style="color:#f92672">links</span>:
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;cloud:eth1&#34;</span>, <span style="color:#e6db74">&#34;internet:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;cloud:eth2&#34;</span>, <span style="color:#e6db74">&#34;ispa:e1-1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;cloud:eth3&#34;</span>, <span style="color:#e6db74">&#34;ispb:e1-1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;ispa:e1-2&#34;</span>, <span style="color:#e6db74">&#34;cmpa1:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;ispb:e1-2&#34;</span>, <span style="color:#e6db74">&#34;cmpa2:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;cmpa1:eth2&#34;</span>, <span style="color:#e6db74">&#34;cmpa2:eth2&#34;</span>]

</code></pre></div><p>For starters I&rsquo;ll configure the internet node(ExaBGP) with an IP address and activate ExaBGP. In this case not advertising routes, just validating peering.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">root@internet:/# cat bgp.cfg
#process announce-routes {
#    run python3 fullbgptable.py;
#    encoder json;
#}

template {
    neighbor AS_65000 {
        router-id 172.16.2.1;
        local-as 65000;
        local-address 172.16.2.1;
    }
}

neighbor 172.16.2.2 {
    inherit AS_65000;
    peer-as 65001;
}

neighbor 172.16.2.3 {
    inherit AS_65000;
    peer-as 65002;
}
root@internet:/#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx@containerlab:~$ docker exec -it internet bash
root@internet:/# ifconfig eth1 172.16.2.1 netmask 255.255.255.0 up
root@internet:/# exabgp bgp.cfg
04:47:01 | <span style="color:#ae81ff">22</span>     | welcome       | Thank you <span style="color:#66d9ef">for</span> using ExaBGP
04:47:01 | <span style="color:#ae81ff">22</span>     | version       | 4.2.17
04:47:01 | <span style="color:#ae81ff">22</span>     | interpreter   | 3.8.10 <span style="color:#f92672">(</span>default, Nov <span style="color:#ae81ff">26</span> 2021, 20:14:08<span style="color:#f92672">)</span>  <span style="color:#f92672">[</span>GCC 9.3.0<span style="color:#f92672">]</span>
04:47:01 | <span style="color:#ae81ff">22</span>     | os            | Linux internet 5.4.0-99-generic <span style="color:#75715e">#112-Ubuntu SMP Thu Feb 3 13:50:55 UTC 2022 x86_64</span>
04:47:01 | <span style="color:#ae81ff">22</span>     | installation  | /usr/local
04:47:01 | <span style="color:#ae81ff">22</span>     | advice        | environment file missing
04:47:01 | <span style="color:#ae81ff">22</span>     | advice        | generate it using <span style="color:#e6db74">&#34;exabgp --fi &gt; /usr/local/etc/exabgp/exabgp.env&#34;</span>
04:47:01 | <span style="color:#ae81ff">22</span>     | cli           | could not find the named pipes <span style="color:#f92672">(</span>exabgp.in and exabgp.out<span style="color:#f92672">)</span> required <span style="color:#66d9ef">for</span> the cli
04:47:01 | <span style="color:#ae81ff">22</span>     | cli           | we scanned the following folders <span style="color:#f92672">(</span>the number is your PID<span style="color:#f92672">)</span>:
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /run/exabgp/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /run/0/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /run/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /var/run/exabgp/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /var/run/0/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /var/run/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /usr/local/run/exabgp/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /usr/local/run/0/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /usr/local/run/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /usr/local/var/run/exabgp/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /usr/local/var/run/0/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   |  - /usr/local/var/run/
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   | please make them in one of the folder with the following commands:
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   | &gt; mkfifo //run/exabgp.<span style="color:#f92672">{</span>in,out<span style="color:#f92672">}</span>
04:47:01 | <span style="color:#ae81ff">22</span>     | cli control   | &gt; chmod <span style="color:#ae81ff">600</span> //run/exabgp.<span style="color:#f92672">{</span>in,out<span style="color:#f92672">}</span>
04:47:01 | <span style="color:#ae81ff">22</span>     | configuration | performing reload of exabgp 4.2.17
04:47:01 | <span style="color:#ae81ff">22</span>     | reactor       | loaded new configuration successfully
04:47:01 | <span style="color:#ae81ff">22</span>     | reactor       | connected to peer-1 with outgoing-1 172.16.2.1-172.16.2.2
04:47:01 | <span style="color:#ae81ff">22</span>     | reactor       | connected to peer-2 with outgoing-1 172.16.2.1-172.16.2.3

</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@containerlab:~$ docker exec -it ispa sr_cli
Using configuration file(s): []
Welcome to the srlinux CLI.
Type &#39;help&#39; (and press &lt;ENTER&gt;) if you need any help using this.
--{ running }--[  ]--
A:ispa# show network-instance default protocols bgp neighbor
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
BGP neighbor summary for network-instance &#34;default&#34;
Flags: S static, D dynamic, L discovered by LLDP, B BFD enabled, - disabled, * slow
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+-------------------+---------------------------+-------------------+-------+----------+---------------+---------------+--------------+---------------------------+
|     Net-Inst      |           Peer            |       Group       | Flags | Peer-AS  |     State     |    Uptime     |   AFI/SAFI   |      [Rx/Active/Tx]       |
+===================+===========================+===================+=======+==========+===============+===============+==============+===========================+
| default           | 10.0.0.1                  | cmpa              | S     | 65003    | established   | 0d:0h:4m:51s  | ipv4-unicast | [0/0/2]                   |
| default           | 172.16.2.1                | internet          | S     | 65000    | established   | 0d:0h:3m:21s  | ipv4-unicast | [0/0/2]                   |
+-------------------+---------------------------+-------------------+-------+----------+---------------+---------------+--------------+---------------------------+
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Summary:
2 configured neighbors, 2 configured sessions are established,0 disabled peers
0 dynamic peers
--{ running }--[  ]--
A:ispa#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@containerlab:~$ docker exec -it ispb sr_cli
Using configuration file(s): []
Welcome to the srlinux CLI.
Type &#39;help&#39; (and press &lt;ENTER&gt;) if you need any help using this.
--{ running }--[  ]--
A:ispb# show network-instance default protocols bgp neighbor
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
BGP neighbor summary for network-instance &#34;default&#34;
Flags: S static, D dynamic, L discovered by LLDP, B BFD enabled, - disabled, * slow
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+-------------------+---------------------------+-------------------+-------+----------+---------------+---------------+--------------+---------------------------+
|     Net-Inst      |           Peer            |       Group       | Flags | Peer-AS  |     State     |    Uptime     |   AFI/SAFI   |      [Rx/Active/Tx]       |
+===================+===========================+===================+=======+==========+===============+===============+==============+===========================+
| default           | 10.0.0.2                  | cmpa              | S     | 65003    | established   | 0d:0h:6m:18s  | ipv4-unicast | [0/0/2]                   |
| default           | 172.16.2.1                | internet          | S     | 65000    | established   | 0d:0h:4m:49s  | ipv4-unicast | [0/0/2]                   |
+-------------------+---------------------------+-------------------+-------+----------+---------------+---------------+--------------+---------------------------+
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Summary:
2 configured neighbors, 2 configured sessions are established,0 disabled peers
0 dynamic peers
--{ running }--[  ]--
A:ispb#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@containerlab:~$ docker exec -it cmpa1 Cli
cmpa1&gt;en
cmpa1#show ip bgp summary
BGP summary information for VRF default
Router identifier 10.0.0.1, local AS number 65003
Neighbor Status Codes: m - Under maintenance
  Neighbor V AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  10.0.0.0 4 65001             24        29    0    0 00:10:22 Estab   2      2
  10.0.0.7 4 65003             17        16    0    0 00:10:22 Estab   2      2
cmpa1#
</code></pre></div><h3 id="advertising-many-nlri">Advertising Many NLRI</h3>
<p>At this point all peering has been verified. I will reenable ExaBGP but advertise all the routes this time. This process does take some time to complete and I will explain a few caveats at the end of this post.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">root@internet:/# cat bgp.cfg
process announce-routes {
    run python3 fullbgptable.py;
    encoder json;
}

template {
    neighbor AS_65000 {
        router-id 172.16.2.1;
        local-as 65000;
        local-address 172.16.2.1;
    }
}

neighbor 172.16.2.2 {
    inherit AS_65000;
    peer-as 65001;
}

neighbor 172.16.2.3 {
    inherit AS_65000;
    peer-as 65002;
}
root@internet:/# exabgp bgp.cfg
</code></pre></div><p>I&rsquo;ll save your eyes, the output is a lot! For fun, lets check out some crazy prefix numbers on one ISP node and one Company A node.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">A:ispa# show network-instance default protocols bgp summary
------------------------------------------------------------------------------------------------------------------------------------------------
BGP is enabled and up in network-instance &#34;default&#34;
Global AS number  : 65001
BGP identifier    : 172.16.2.2
------------------------------------------------------------------------------------------------------------------------------------------------
  Total paths               : 990
  Received routes           : 74867
  Received and active routes: 74467
  Total UP peers            : 2
  Configured peers          : 2, 0 are disabled
  Dynamic peers             : None
------------------------------------------------------------------------------------------------------------------------------------------------
--{ running }--[  ]--
A:ispa#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa1#show ip bgp summary
BGP summary information for VRF default
Router identifier 10.0.0.1, local AS number 65003
Neighbor Status Codes: m - Under maintenance
  Neighbor V AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  10.0.0.0 4 65001           1672       721    0    0 00:21:26 Estab   87670  87670
  10.0.0.7 4 65003           1684      1668    0    0 00:21:26 Estab   86701  86701
cmpa1#
</code></pre></div><p>That is beautiful, 74K routes on ISPA and 87K routes on Company A node.</p>
<h2 id="use-cases">Use Cases</h2>
<h3 id="reducing-excess-information-client-side">Reducing Excess Information Client Side</h3>
<p>Let me walk through a few examples on how this could be useful in a lab scenario or for general learning. You may have noticed that the Company A node is receiving routes from both ISPA and the iBGP neighbor. Seems like a bit of a waste in resources and memory. We can configure the iBGP neighbors to only send the default route from the ISP. The below configuration is done on both node 1 and 2 of Company A.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">ip prefix-list default seq 10 permit 0.0.0.0/0
!
route-map default permit 10
   match ip address prefix-list default
router bgp 65003
   neighbor 10.0.0.7 route-map default out
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa1#show ip bgp summary
BGP summary information for VRF default
Router identifier 10.0.0.1, local AS number 65003
Neighbor Status Codes: m - Under maintenance
  Neighbor V AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  10.0.0.0 4 65001           8341      7566    0    0 00:31:11 Estab   18898  18898
  10.0.0.7 4 65003           8540      8454    0    0 00:31:11 Estab   1      1
cmpa1#
</code></pre></div><p>Now we are only receiving the default route from our iBGP neighbor! This iBGP route map also has the benefit of preventing our AS from being a transit AS. Routes learned from one node will not be advertised to the other ISP and possibly severely hindering our edge devices. Maybe we want to filter the routes inbound from the ISPs.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 65003
   neighbor 10.0.0.0 route-map default in
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa1#show ip bgp sum
BGP summary information for VRF default
Router identifier 10.0.0.1, local AS number 65003
Neighbor Status Codes: m - Under maintenance
  Neighbor V AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  10.0.0.0 4 65001          30589      7657    0    0 01:09:18 Estab   257372 1
  10.0.0.7 4 65003           8585      8499    0    0 01:09:17 Estab   1      1
cmpa1#show ip bgp | b Network
          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;      0.0.0.0/0              10.0.0.0              0       -          100     0       65001 199524 3257 i
 *        0.0.0.0/0              10.0.0.7              0       -          100     0       65002 199524 3257 i
cmpa1#
</code></pre></div><h3 id="reducing-excess-information-provider-side">Reducing Excess Information Provider Side</h3>
<p>Maybe we have a really simple site and we no longer need full tables. Our days of being fancy are over. I&rsquo;ll remove the previous configuration besides the iBGP route-map. Then we will update the ISP to only send the default.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@containerlab:~$ docker exec -it ispa sr_cli
Using configuration file(s): []
Welcome to the srlinux CLI.
Type &#39;help&#39; (and press &lt;ENTER&gt;) if you need any help using this.
--{ running }--[  ]--
A:ispa# enter candidate
--{ candidate shared default }--[  ]--
A:ispa# routing-policy
--{ candidate shared default }--[ routing-policy ]--
A:ispa# set policy default-route default-action reject
A:ispa# set policy default-route statement 10 match prefix-set default
A:ispa# set policy default-route statement 10 action accept
A:ispa# set prefix-set default prefix 0.0.0.0/0 mask-length-range exact
A:ispa# /network-instance default protocols bgp
--{ * candidate shared default }--[ network-instance default protocols bgp ]--
A:ispa# set group cmpa export-policy default-route
A:ispa# commit save
/system:
    Saved current running configuration as initial (startup) configuration &#39;/etc/opt/srlinux/config.json&#39;

All changes have been committed. Leaving candidate mode.
--{ running }--[ network-instance default protocols bgp ]--
A:ispa# show neighbor 10.0.0.1 advertised-routes ipv4
------------------------------------------------------------------------------------------------------------------------------------------------
Peer        : 10.0.0.1, remote AS: 65003, local AS: 65001
Type        : static
Description : None
Group       : cmpa
------------------------------------------------------------------------------------------------------------------------------------------------
Origin codes: i=IGP, e=EGP, ?=incomplete
+---------------------------------------------------------------------------------------------------------------------------------------------+
|          Network                  Next Hop              MED           LocPref                       AsPath                       Origin     |
+=============================================================================================================================================+
| 0.0.0.0/0                    10.0.0.0                    -              100        [65001, 199524, 3257]                            i       |
+---------------------------------------------------------------------------------------------------------------------------------------------+
------------------------------------------------------------------------------------------------------------------------------------------------
1 advertised BGP routes
------------------------------------------------------------------------------------------------------------------------------------------------
--{ running }--[ network-instance default protocols bgp ]--
A:ispa#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa1#  show ip bgp sum
BGP summary information for VRF default
Router identifier 10.0.0.1, local AS number 65003
Neighbor Status Codes: m - Under maintenance
  Neighbor V AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  10.0.0.0 4 65001          35789      7711    0    0 01:30:56 Estab   1      1
  10.0.0.7 4 65003           8615      8526    0    0 01:30:56 Estab   1      1
cmpa1#
</code></pre></div><h3 id="outbound-traffic-steering">Outbound Traffic Steering</h3>
<h4 id="partial-routes-on-each-node-by-as-path-length">Partial Routes on Each Node by AS Path Length</h4>
<p>Maybe we want to control outbound paths on each Company A node. One node gets routes with AS path length of three or less with a default route. The other will get routes with an AS length of four or more and a default. The iBGP route map will be removed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text"># cmpa1
ip prefix-list default seq 10 permit 0.0.0.0/0
!
route-map default permit 5
   match as-path length &lt;= 3
!
route-map default permit 10
   match ip address prefix-list default
!
router bgp 65003
   neighbor 10.0.0.0 route-map default in

# cmpa2
ip prefix-list default seq 10 permit 0.0.0.0/0
!
route-map default permit 5
   match as-path length &gt;= 4 and &lt;= 4000
!
route-map default permit 10
   match ip address prefix-list default
!
router bgp 65003
   neighbor 10.0.0.3 route-map default in
</code></pre></div><p>I&rsquo;ll share a small sample from active BGP NLRI on each node. Note the AS path lengh on that information.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa1#show ip bgp neighbors 10.0.0.0 routes | b AS Path
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;      0.0.0.0/0              10.0.0.0              0       -          100     0       65001 199524 3257 i
 * &gt;      1.0.0.0/24             10.0.0.0              0       -          100     0       65001 52320 13335 i
 * &gt;      1.1.1.0/24             10.0.0.0              0       -          100     0       65001 52320 13335 i
 * &gt;      1.9.0.0/16             10.0.0.0              0       -          100     0       65001 6939 4788 i
 * &gt;      1.12.0.0/20            10.0.0.0              0       -          100     0       65001 52320 132203 i
 * &gt;      1.12.34.0/23           10.0.0.0              0       -          100     0       65001 52320 132203 i
 * &gt;      1.37.0.0/16            10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.0.0/17            10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.27.0/24           10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.32.0/24           10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.35.0/24           10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.36.0/22           10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.47.0/24           10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.86.0/24           10.0.0.0              0       -          100     0       65001 6939 4775 i
 * &gt;      1.37.128.0/17          10.0.0.0              0       -          100     0       65001 6939 4775 i
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa2#show ip bgp neighbors 10.0.0.3 routes | b AS Path
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;      0.0.0.0/0              10.0.0.3              0       -          100     0       65002 199524 3257 i
 * &gt;      1.0.4.0/22             10.0.0.3              0       -          100     0       65002 6939 4826 38803 i
 * &gt;      1.0.4.0/24             10.0.0.3              0       -          100     0       65002 6939 4826 38803 i
 * &gt;      1.0.5.0/24             10.0.0.3              0       -          100     0       65002 6939 4826 38803 i
 * &gt;      1.0.6.0/24             10.0.0.3              0       -          100     0       65002 6939 4826 38803 i
 * &gt;      1.0.7.0/24             10.0.0.3              0       -          100     0       65002 6939 4826 38803 i
 * &gt;      1.0.64.0/18            10.0.0.3              0       -          100     0       65002 52320 1299 2497 7670 18144 i
 * &gt;      1.0.128.0/17           10.0.0.3              0       -          100     0       65002 6939 38040 23969 i
 * &gt;      1.0.128.0/18           10.0.0.3              0       -          100     0       65002 6939 38040 23969 i
 * &gt;      1.0.128.0/19           10.0.0.3              0       -          100     0       65002 6939 38040 23969 i
 * &gt;      1.0.128.0/24           10.0.0.3              0       -          100     0       65002 6939 4651 23969 i
 * &gt;      1.0.129.0/24           10.0.0.3              0       -          100     0       65002 6939 4651 23969 i
 * &gt;      1.0.130.0/23           10.0.0.3              0       -          100     0       65002 6939 38040 23969 i
 * &gt;      1.0.132.0/24           10.0.0.3              0       -          100     0       65002 6939 38040 23969 i
 * &gt;      1.0.133.0/24           10.0.0.3              0       -          100     0       65002 6939 38040 23969 i
</code></pre></div><h4 id="partial-routes-on-each-node-by-prefix">Partial Routes on Each Node by Prefix</h4>
<p>Please see the updated configuration on each node and their respective NLRI.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text"># cmpa1
ip prefix-list default seq 10 permit 0.0.0.0/0
ip prefix-list half seq 10 permit 0.0.0.0/1 le 32
!
route-map default permit 5
   match ip address prefix-list half
!
route-map default permit 10
   match ip address prefix-list default
!
router bgp 65003
   neighbor 10.0.0.0 route-map default in

# cmpa2
ip prefix-list default seq 10 permit 0.0.0.0/0
ip prefix-list half seq 10 permit 128.0.0.0/2 le 32
!
route-map default permit 5
   match ip address prefix-list half
!
route-map default permit 10
   match ip address prefix-list default
!
router bgp 65003
   neighbor 10.0.0.3 route-map default in
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa1# show ip bgp neighbors 10.0.0.0 routes | b AS Path
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;      0.0.0.0/0              10.0.0.0              0       -          100     0       65001 199524 3257 i
 * &gt;      1.0.0.0/24             10.0.0.0              0       -          100     0       65001 52320 13335 i
 * &gt;      1.0.4.0/22             10.0.0.0              0       -          100     0       65001 6939 4826 38803 i
 * &gt;      1.0.4.0/24             10.0.0.0              0       -          100     0       65001 6939 4826 38803 i
 * &gt;      1.0.5.0/24             10.0.0.0              0       -          100     0       65001 6939 4826 38803 i
 * &gt;      1.0.6.0/24             10.0.0.0              0       -          100     0       65001 6939 4826 38803 i
 * &gt;      1.0.7.0/24             10.0.0.0              0       -          100     0       65001 6939 4826 38803 i
 * &gt;      1.0.64.0/18            10.0.0.0              0       -          100     0       65001 52320 1299 2497 7670 18144 i
 * &gt;      1.0.128.0/17           10.0.0.0              0       -          100     0       65001 6939 38040 23969 i
 * &gt;      1.0.128.0/18           10.0.0.0              0       -          100     0       65001 6939 38040 23969 i
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cmpa2#show ip bgp neighbors 10.0.0.3 routes | b AS Path
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;      0.0.0.0/0              10.0.0.3              0       -          100     0       65002 199524 3257 i
 * &gt;      128.0.1.0/24           10.0.0.3              0       -          100     0       65002 52320 9009 i
 * &gt;      128.0.116.0/24         10.0.0.3              0       -          100     0       65002 52320 1299 57878 i
 * &gt;      128.0.118.0/24         10.0.0.3              0       -          100     0       65002 52320 1299 57878 i
 * &gt;      128.1.36.0/24          10.0.0.3              0       -          100     0       65002 6939 4766 i
 * &gt;      128.1.76.0/24          10.0.0.3              0       -          100     0       65002 6939 4766 i
 * &gt;      128.28.0.0/16          10.0.0.3              0       -          100     0       65002 52320 2914 2514 2514 i
 * &gt;      128.45.0.0/16          10.0.0.3              0       -          100     0       65002 52320 1267 24608 i
 * &gt;      128.53.0.0/16          10.0.0.3              0       -          100     0       65002 52320 2914 2514 2514 i
 * &gt;      128.65.96.0/21         10.0.0.3              0       -          100     0       65002 6939 42010 i
</code></pre></div><h2 id="caveats">Caveats</h2>
<p>Now for the issues I had with ExaBGP. I really shouldn&rsquo;t say issues, the things I ran into were mostly user error. I&rsquo;ve spent about two days with it and I really like it so far. Like anything I write, this was just a tiny fraction of what ExaBGP can do. If you see some error in the Dockerfile or process in general, please reach out to me. I&rsquo;d love to update this and get it nicely packaged into something useful. One error I ran into, at some point in the NLRI announcements, ExaBGP will just stop sending or keep a peer session established. It&rsquo;s something I haven&rsquo;t figured out and if someone more familiar with the tool can give me some insight, I&rsquo;m all ears!</p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far. Really means the world to me and writing this one was a joy. Anytime I get to play with BGP in the lab is a good time. So much to learn with BGP&hellip; I tried to include links of posts or documents that helped me throughout this small journey. Please check them out if you have time. Below are some additional links that helped me out a lot!</p>
<ul>
<li><a href="https://github.com/Exa-Networks/exabgp">ExaBGP GitHub</a></li>
<li><a href="https://thepacketgeek.com/exabgp/">ExaBGP Posts by Mat Wood</a></li>
<li><a href="https://jasonmurray.org/posts/2020/exabgp-fulltable/">ExaBGP fulltable by Jaon Murray</a></li>
<li><a href="https://data.ris.ripe.net/rrc16/">RRC16 BGP Information</a></li>
<li><a href="https://github.com/t2mune/mrtparse/tree/master/examples">mrtparse, Convert BGP Information to ExaBGP Format</a></li>
<li><a href="https://jasonet.co/posts/scheduled-actions/">Run Actions on a Shedule by Jason Etcovitch</a></li>
<li><a href="https://netcraftsmen.com/internet-edge-traffic-steering-part-1/">Internet Edge: Traffic Steering Part 1-3 by Peter Welcher</a></li>
<li><a href="https://hub.docker.com/repository/docker/juliopdx/exabgp-irt">ExaBGP-IRT Docker Hub</a></li>
<li><a href="https://github.com/JulioPDX/learning_labs/tree/main/labs/bgp/exabgp">Containerlab Clab File Used in Blog</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Network Simulation Tools and Containerlab</title>
            <link>https://juliopdx.com/2022/02/13/network-simulation-tools-and-containerlab/</link>
            <pubDate>Sun, 13 Feb 2022 21:07:43 -0800</pubDate>
            
            <guid>https://juliopdx.com/2022/02/13/network-simulation-tools-and-containerlab/</guid>
            <description>Introduction I&amp;rsquo;ve been progressing through a series of technical books, some of which I&amp;rsquo;ve shared on other blogs. A few of them focus on BGP. BGP being so broad, I decided to create a challenge lab. Creating the challenge/troubleshooting labs has really made more concepts stick. I&amp;rsquo;m trying to use the principle of teaching someone to make the learning last. These have been incredibly fun to create and the community interaction has been amazing.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I&rsquo;ve been progressing through a series of technical books, some of which I&rsquo;ve shared on other blogs. A few of them focus on BGP. BGP being so broad, I decided to create a <a href="https://github.com/JulioPDX/learning_labs/tree/main/labs/bgp/arista">challenge lab</a>. Creating the challenge/troubleshooting labs has really made more concepts stick. I&rsquo;m trying to use the principle of teaching someone to make the learning last. These have been incredibly fun to create and the community interaction has been amazing. One brave soul(Jeroen van Bemmel) shared his <a href="https://github.com/jbemmel/netsim-examples/tree/evpn-vxlan-to-hosts/BGP/Julio-Challenge">solution</a>. I was fascinated on his solution and how he created his topology with Containerlab and net-sim tools.</p>
<h2 id="net-sim-tools">net-sim tools</h2>
<p>Network Simulation Tools was created by Ivan Pepelnjak of <a href="https://www.ipspace.net/Main_Page">ipspace.net</a>. I think Ivan was tired of the point and click required when making a lab topology with other tools. The aim of the tool is to take infrastructure as code principles and leverage them for the creation of network topologies. Topologies can be defined in a simple YAML file and net-sim will take care of link connections, addressing(IPv4/6), routing protocols, etc&hellip;</p>
<p>Usually when creating a lab, there is a slow process of starting up nodes, connecting links, selecting address space, configuring hostname Rx, configuring ip add 10.x.x.x 255.255.255.0, no shut, and so on and so forth. At this point, I don&rsquo;t think I need to configure an IP address on an interface to know I&rsquo;ve mastered that skill :).</p>
<h3 id="getting-started">Getting Started</h3>
<p>In this post we will leverage Containerlab with net-sim tools and below are the requirements to get started.</p>
<ul>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04">Install Docker</a></li>
<li><a href="https://containerlab.srlinux.dev/install/">Install Containerlab</a></li>
<li><a href="https://juliopdx.com/2021/12/10/my-journey-and-experience-with-containerlab/">Import Docker Container Images</a></li>
</ul>
<p>There are a few options to get started with net-sim tools, but I think using a python virtualenv was fairly simple.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">python3 -m venv venv
source venv/bin/activate
pip install netsim-tools
pip install ansible <span style="color:#75715e">#decided to use whatever was latest and it works just fine for me</span>
</code></pre></div><p>I know this seems like a lot but the benefits are definitely worth it.</p>
<h3 id="defining-a-topology">Defining a Topology</h3>
<p>Let me share a basic topology and walk you through what will be created. Please note, some of the options I have defined are already defaults, I just decided to add them to the topology file for reader clarity.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">basic</span>
<span style="color:#f92672">provider</span>: <span style="color:#ae81ff">clab</span>
<span style="color:#f92672">defaults</span>:
  <span style="color:#f92672">device</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">devices</span>:
    <span style="color:#f92672">eos.clab.image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>


<span style="color:#f92672">addressing</span>:
  <span style="color:#f92672">loopback</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#ae81ff">200</span>::<span style="color:#ae81ff">/7</span>
  <span style="color:#f92672">router_id</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">32</span>
  <span style="color:#f92672">p2p</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.0</span><span style="color:#ae81ff">/16</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">30</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>

<span style="color:#f92672">nodes</span>: [<span style="color:#ae81ff">R1, R2, R3]</span>

<span style="color:#f92672">links</span>:
  - <span style="color:#ae81ff">R1-R2</span>
  - <span style="color:#ae81ff">R2-R3</span>

</code></pre></div><p>This Topology will perform the following:</p>
<ul>
<li>Use Containerlab to deploy our nodes</li>
<li>Sets a default image version for EOS devices(saves a lot of typing)</li>
<li>Set prefix information for different connections, not required since net-sim will do this for you.</li>
<li>Define how many nodes we want</li>
<li>Define how we want the nodes connected.</li>
</ul>
<p>A few things to note. I defined the IP space but that is not required when creating lab topologies. No need to worry about interface names, the logic within net-sim tools will just increment over the next available interface.</p>
<h4 id="providers">Providers</h4>
<p>At the time of this writing, net-sim tools supports three popular providers; vagrant-libvirt, Vagrant VirtualBox, and Containerlab. As long as the user is familiar with their provider of choice, the interaction with net-sim tools is similar across the board. In the example topology file above, we defined Containerlab as our provider. This will cause net-sim tools to create a <code>clab.yml</code> file in the structure Containerlab is used to. This <code>clab.yml</code> file will then be used to deploy our network devices.</p>
<h4 id="images">Images</h4>
<p>Net-sim does not come pre packaged with any network operating systems(NOS) images. A few vendors have made their images publicly available(see Nokia/FRR) as a simple docker pull. For any other images, please see their documentation to obtain an image. For this example, I will be using some Arista cEOS nodes. They can be obtained by signing up to their support site(free).</p>
<h4 id="networks">Networks</h4>
<p>Net-sim gives you pretty much any option you could want for network addressing. You can either use built-in address pools, assign statics, or define your own pools. Net-sim will then handle the IP addressing for each network device and interface. Very simple example below, please check out their in-depth <a href="https://netsim-tools.readthedocs.io/en/latest/example/addressing-tutorial.html">addressing tutorial</a>!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml">  <span style="color:#f92672">p2p</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.0</span><span style="color:#ae81ff">/16</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">30</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>

<span style="color:#f92672">nodes</span>: [<span style="color:#ae81ff">R1, R2, R3]</span>

<span style="color:#f92672">links</span>:
  - <span style="color:#ae81ff">R1-R2</span>
  - <span style="color:#ae81ff">R2-R3</span>
</code></pre></div><div class="mermaid">
    
graph LR
  R1((R1)) --- R2((R2))
  R2 --- R3((R3))

</div>
<p>Here we have three nodes with two point to point links. R1 will have node ID 1, R2 will be node ID 2, etc&hellip; The first <code>/30</code> prefix in the <code>10.1.0.0/16</code> network will be <code>10.1.0.0-3</code>. This will be assigned to the link between R1-R2. The next available <code>/30</code> will be <code>10.1.0.4-7</code>. Same as before, this network will be assigned to the R2-R3 link.</p>
<h3 id="deployments-and-how-it-all-works">Deployments and How It All Works</h3>
<p>The CLI has a few neat options, let me show you some I think will be used the most.</p>
<p><code>netlab create</code></p>
<p>This command will create any files necessary to deploy your containers and configure devices with Ansible. Great if you would just like to view the data without actually performing any deployments.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ netlab create basic.yml
Created provider configuration file: clab.yml
Created group_vars <span style="color:#66d9ef">for</span> all
Created group_vars <span style="color:#66d9ef">for</span> eos
Created host_vars <span style="color:#66d9ef">for</span> R1
Created host_vars <span style="color:#66d9ef">for</span> R2
Created host_vars <span style="color:#66d9ef">for</span> R3
Created minimized Ansible inventory hosts.yml
Created Ansible configuration file: ansible.cfg
<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$
</code></pre></div><p>Lets take a look at the <code>clab.yml</code> file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">basic</span>

<span style="color:#f92672">topology</span>:
  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">R1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>

    <span style="color:#f92672">R2</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>

    <span style="color:#f92672">R3</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>



  <span style="color:#f92672">links</span>:
  - <span style="color:#f92672">endpoints</span>:
    - <span style="color:#e6db74">&#34;R1:eth1&#34;</span>
    - <span style="color:#e6db74">&#34;R2:eth1&#34;</span>
  - <span style="color:#f92672">endpoints</span>:
    - <span style="color:#e6db74">&#34;R2:eth2&#34;</span>
    - <span style="color:#e6db74">&#34;R3:eth1&#34;</span>

</code></pre></div><p>This can then be used to run either <code>netlab up basic.yml</code> or <code>sudo containerlab deploy -t clab.yml</code>. One of the more interesting files created is the <code>host_vars/&lt;device name&gt;/topology.yml</code>. This gives you a great look at everything that will be deployed on the device with Ansible</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#75715e"># Ansible inventory created from [&#39;basic.yml&#39;, &#39;package:topology-defaults.yml&#39;]</span>
<span style="color:#75715e">#</span>
---
<span style="color:#f92672">af</span>:
  <span style="color:#f92672">ipv4</span>: <span style="color:#66d9ef">true</span>
  <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>
<span style="color:#f92672">box</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>
<span style="color:#f92672">clab</span>:
  <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
<span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">clab-basic-R1</span>
<span style="color:#f92672">interfaces</span>:
- <span style="color:#f92672">clab</span>:
    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">eth1</span>
  <span style="color:#f92672">ifindex</span>: <span style="color:#ae81ff">1</span>
  <span style="color:#f92672">ifname</span>: <span style="color:#ae81ff">Ethernet1</span>
  <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.1</span><span style="color:#ae81ff">/30</span>
  <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>
  <span style="color:#f92672">linkindex</span>: <span style="color:#ae81ff">1</span>
  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">R1 -&gt; R2</span>
  <span style="color:#f92672">neighbors</span>:
  - <span style="color:#f92672">ifname</span>: <span style="color:#ae81ff">Ethernet1</span>
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.2</span><span style="color:#ae81ff">/30</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>
    <span style="color:#f92672">node</span>: <span style="color:#ae81ff">R2</span>
  <span style="color:#f92672">remote_id</span>: <span style="color:#ae81ff">2</span>
  <span style="color:#f92672">remote_ifindex</span>: <span style="color:#ae81ff">1</span>
  <span style="color:#f92672">type</span>: <span style="color:#ae81ff">p2p</span>
<span style="color:#f92672">loopback</span>:
  <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.1</span><span style="color:#ae81ff">/32</span>
  <span style="color:#f92672">ipv6</span>: <span style="color:#ae81ff">200</span>:<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">1</span>::<span style="color:#ae81ff">1</span><span style="color:#ae81ff">/64</span>
<span style="color:#f92672">mgmt</span>:
  <span style="color:#f92672">ifname</span>: <span style="color:#ae81ff">Management0</span>
  <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">192.168.121.101</span>
  <span style="color:#f92672">mac</span>: <span style="color:#ae81ff">08</span>-<span style="color:#ae81ff">4F-A9-00-00-01</span>

</code></pre></div><p><code>netlab up</code></p>
<p>This command will actually deploy our nodes with Containerlab and configure the initial configuration with Ansible. You can use this command instead of using individual commands to deploy the topology.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ netlab up basic.yml
Created provider configuration file: clab.yml
Created group_vars <span style="color:#66d9ef">for</span> all
Created group_vars <span style="color:#66d9ef">for</span> eos
Created host_vars <span style="color:#66d9ef">for</span> R1
Created host_vars <span style="color:#66d9ef">for</span> R2
Created host_vars <span style="color:#66d9ef">for</span> R3
Created minimized Ansible inventory hosts.yml
Created Ansible configuration file: ansible.cfg

Step 2: Checking virtualization provider installation
<span style="color:#f92672">============================================================</span>
.. all tests succeeded, moving on

Step 3: starting the lab
<span style="color:#f92672">============================================================</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Containerlab v0.23.0 started
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Parsing &amp; checking topology file: clab.yml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating lab directory: /home/juliopdx/repos/cl-nst-demo/clab-basic
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating docker network: Name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;clab&#39;</span>, IPv4Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;172.20.20.0/24&#39;</span>, IPv6Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;2001:172:20:20::/64&#39;</span>, MTU<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;1500&#39;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R2
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R1
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R3
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: R1:eth1 &lt;--&gt; R2:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: R2:eth2 &lt;--&gt; R3:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R1&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R2&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R3&#39;</span> node
INFO<span style="color:#f92672">[</span>0053<span style="color:#f92672">]</span> Adding containerlab host entries to /etc/hosts file
+---+---------------+--------------+--------------+------+---------+----------------+----------------------+
| <span style="color:#75715e"># |     Name      | Container ID |    Image     | Kind |  State  |  IPv4 Address  |     IPv6 Address     |</span>
+---+---------------+--------------+--------------+------+---------+----------------+----------------------+
| <span style="color:#ae81ff">1</span> | clab-basic-R1 | ffb8857ca087 | ceos:4.27.2F | ceos | running | 172.20.20.2/24 | 2001:172:20:20::2/64 |
| <span style="color:#ae81ff">2</span> | clab-basic-R2 | c533fc2411e0 | ceos:4.27.2F | ceos | running | 172.20.20.3/24 | 2001:172:20:20::3/64 |
| <span style="color:#ae81ff">3</span> | clab-basic-R3 | da9fedb7ce7f | ceos:4.27.2F | ceos | running | 172.20.20.4/24 | 2001:172:20:20::4/64 |
+---+---------------+--------------+--------------+------+---------+----------------+----------------------+

Step 4: deploying initial device configurations
<span style="color:#f92672">============================================================</span>

PLAY <span style="color:#f92672">[</span>Deploy device configuration<span style="color:#f92672">]</span> *************************************************************

TASK <span style="color:#f92672">[</span>Find initial configuration template<span style="color:#f92672">]</span> *****************************************************
skipping: <span style="color:#f92672">[</span>R1<span style="color:#f92672">]</span> <span style="color:#f92672">=</span>&gt; <span style="color:#f92672">(</span>item<span style="color:#f92672">=</span>/home/juliopdx/repos/cl-nst-demo/venv/lib/python3.8/site-packages/netsim/ansible/templates/initial/eos.j2<span style="color:#f92672">)</span>
skipping: <span style="color:#f92672">[</span>R1<span style="color:#f92672">]</span>
skipping: <span style="color:#f92672">[</span>R3<span style="color:#f92672">]</span> <span style="color:#f92672">=</span>&gt; <span style="color:#f92672">(</span>item<span style="color:#f92672">=</span>/home/juliopdx/repos/cl-nst-demo/venv/lib/python3.8/site-packages/netsim/ansible/templates/initial/eos.j2<span style="color:#f92672">)</span>
skipping: <span style="color:#f92672">[</span>R3<span style="color:#f92672">]</span>
skipping: <span style="color:#f92672">[</span>R2<span style="color:#f92672">]</span> <span style="color:#f92672">=</span>&gt; <span style="color:#f92672">(</span>item<span style="color:#f92672">=</span>/home/juliopdx/repos/cl-nst-demo/venv/lib/python3.8/site-packages/netsim/ansible/templates/initial/eos.j2<span style="color:#f92672">)</span>
skipping: <span style="color:#f92672">[</span>R2<span style="color:#f92672">]</span>

TASK <span style="color:#f92672">[</span>Deploy initial device configuration<span style="color:#f92672">]</span> *****************************************************
included: /home/juliopdx/repos/cl-nst-demo/venv/lib/python3.8/site-packages/netsim/ansible/tasks/deploy-config/eos.yml <span style="color:#66d9ef">for</span> R2, R1, R3 <span style="color:#f92672">=</span>&gt; <span style="color:#f92672">(</span>item<span style="color:#f92672">=</span>/home/juliopdx/repos/cl-nst-demo/venv/lib/python3.8/site-packages/netsim/ansible/tasks/deploy-config/eos.yml<span style="color:#f92672">)</span>

TASK <span style="color:#f92672">[</span>wait_for_connection<span style="color:#f92672">]</span> *********************************************************************
ok: <span style="color:#f92672">[</span>R2<span style="color:#f92672">]</span>
ok: <span style="color:#f92672">[</span>R1<span style="color:#f92672">]</span>
ok: <span style="color:#f92672">[</span>R3<span style="color:#f92672">]</span>

TASK <span style="color:#f92672">[</span>arista.eos.eos_config<span style="color:#f92672">]</span> *******************************************************************
<span style="color:#f92672">[</span>WARNING<span style="color:#f92672">]</span>: To ensure idempotency and correct diff the input configuration lines should be
similar to how they appear <span style="color:#66d9ef">if</span> present in the running configuration on device including the
indentation
changed: <span style="color:#f92672">[</span>R3<span style="color:#f92672">]</span>
changed: <span style="color:#f92672">[</span>R1<span style="color:#f92672">]</span>
changed: <span style="color:#f92672">[</span>R2<span style="color:#f92672">]</span>

TASK <span style="color:#f92672">[</span>Deploy module-specific configurations<span style="color:#f92672">]</span> ***************************************************

TASK <span style="color:#f92672">[</span>Deploy custom deployment templates<span style="color:#f92672">]</span> ******************************************************

PLAY RECAP *************************************************************************************
R1                         : ok<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span>    changed<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>    unreachable<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    failed<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    skipped<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span>    rescued<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    ignored<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>
R2                         : ok<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span>    changed<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>    unreachable<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    failed<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    skipped<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span>    rescued<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    ignored<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>
R3                         : ok<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span>    changed<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>    unreachable<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    failed<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    skipped<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span>    rescued<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>    ignored<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>

<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$
</code></pre></div><p>Checking the configuration on R1.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ docker exec -it clab-basic-R1 Cli
R1&gt;en
R1#show run
! Command: show running-config
! device: R1 <span style="color:#f92672">(</span>cEOSLab, EOS-4.27.2F-26069621.4272F <span style="color:#f92672">(</span>engineering build<span style="color:#f92672">))</span>
!
no aaa root
!
username admin privilege <span style="color:#ae81ff">15</span> role network-admin secret sha512 $6$KKU2DJRmdlpXm3wG$dZKDhT4eC5ZcNcwyPtklQfNrOnUw28P4dNYTSDB5LiBp/gV0r24ET/D.4L9AeBLmCwcwyMsgtopRkaHigNkHC.
!
transceiver qsfp default-mode 4x10G
!
service routing protocols model multi-agent
!
hostname R1
ip host R2 10.0.0.2 10.1.0.2 10.1.0.5
ip host R3 10.0.0.3 10.1.0.6
!
spanning-tree mode mstp
!
management api http-commands
   no shutdown
!
management api gnmi
   transport grpc default
!
management api netconf
   transport ssh default
!
interface Ethernet1
   description R1 -&gt; R2
   mac-address 52:dc:ca:fe:01:01
   no switchport
   ip address 10.1.0.1/30
   ipv6 enable
!
interface Loopback0
   ip address 10.0.0.1/32
   ipv6 address 200:0:0:1::1/64
!
interface Management0
   ip address 172.20.20.2/24
   ipv6 address 2001:172:20:20::2/64
   no lldp transmit
   no lldp receive
!
ip routing
!
ipv6 unicast-routing
!
end
R1#
</code></pre></div><p>I think that is pretty awesome. With one command we have deployed nodes, set base parameters, and interface configurations. At this point someone could proceed to learn whatever the objective is for the day or test some design.</p>
<p><code>netlab down</code></p>
<p>This command will remove all containers and remove any directory created by Containerlab(startup configs). If you would like to keep startup configurations, I would recommend you shutdown the lab with Containerlab commands.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ netlab down basic.yml

Step 1: Checking virtualization provider installation
<span style="color:#f92672">============================================================</span>
.. all tests succeeded, moving on


Step 2: stopping the lab
<span style="color:#f92672">============================================================</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Parsing &amp; checking topology file: clab.yml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Destroying lab: basic
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Removed container: clab-basic-R1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Removed container: clab-basic-R3
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Removed container: clab-basic-R2
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Removing containerlab host entries from /etc/hosts file
<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$
</code></pre></div><h4 id="python-ansible-and-jinja">Python, Ansible, and Jinja</h4>
<p>Behind the scenes Python is wrapping everything that has been defined into usable data. This can then be used with Ansible and Jinja to create vendor specific configurations. This could be interface settings, BGP, OSPF, and others. For a list of supported configurations by NOS, check it out <a href="https://netsim-tools.readthedocs.io/en/latest/platforms.html#supported-configuration-modules">here</a>. I have probably atrociously oversimplified this process, thankfully the creators made this <a href="https://netsim-tools.readthedocs.io/en/latest/dev/transform.html">in-depth guide</a> on the process.</p>
<h3 id="sample-use-cases">Sample Use Cases</h3>
<p>So far I think this is pretty neat. Similar to Containerlab, I think net-sim lends itself incredibly well in learning environments or testing solutions/designs. Let me walk you through some simple workflows someone might go through when learning a technology.</p>
<h4 id="basic-lab-for-ospf-learning">Basic Lab for OSPF Learning</h4>
<p>We have shown this topology already but bear with me. In this example we have three nodes that we would like to deploy to start learning some OSPF. The topology file can deploy nodes and configure all interface settings.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">basic</span>
<span style="color:#f92672">provider</span>: <span style="color:#ae81ff">clab</span>
<span style="color:#f92672">defaults</span>:
  <span style="color:#f92672">device</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">devices</span>:
    <span style="color:#f92672">eos.clab.image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>


<span style="color:#f92672">addressing</span>:
  <span style="color:#f92672">loopback</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#ae81ff">200</span>::<span style="color:#ae81ff">/7</span>
  <span style="color:#f92672">router_id</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">32</span>
  <span style="color:#f92672">p2p</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.0</span><span style="color:#ae81ff">/16</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">30</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>

<span style="color:#f92672">nodes</span>: [<span style="color:#ae81ff">R1, R2, R3]</span>

<span style="color:#f92672">links</span>:
  - <span style="color:#ae81ff">R1-R2</span>
  - <span style="color:#ae81ff">R2-R3</span>

</code></pre></div><h4 id="multi-area-ospf">Multi Area OSPF</h4>
<p>Maybe we want to deploy an OSPF topology with multiple areas. This could be used to get the concepts of IA vs O vs E vs N routes down. This file will deploy R2 in area 0. R1 will be in area 1, but the link between R1 and R2 will be in area 0. R3 will be in area 3, but the link between R2 and R3 will be in area 0.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">ospf</span>
<span style="color:#f92672">provider</span>: <span style="color:#ae81ff">clab</span>
<span style="color:#f92672">defaults</span>:
  <span style="color:#f92672">device</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">devices</span>:
    <span style="color:#f92672">eos.clab.image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>


<span style="color:#f92672">addressing</span>:
  <span style="color:#f92672">loopback</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#ae81ff">200</span>::<span style="color:#ae81ff">/7</span>
  <span style="color:#f92672">router_id</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">32</span>
  <span style="color:#f92672">p2p</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.0</span><span style="color:#ae81ff">/16</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">30</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>

<span style="color:#f92672">module</span>: [<span style="color:#ae81ff">ospf]</span>
<span style="color:#f92672">ospf</span>:
  <span style="color:#f92672">area</span>: <span style="color:#ae81ff">0</span>

<span style="color:#f92672">nodes</span>:
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">R1</span>
    <span style="color:#f92672">ospf</span>:
      <span style="color:#f92672">area</span>: <span style="color:#ae81ff">1</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">R2</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">R3</span>
    <span style="color:#f92672">ospf</span>:
      <span style="color:#f92672">area</span>: <span style="color:#ae81ff">3</span>

<span style="color:#f92672">links</span>:
  - <span style="color:#f92672">R1</span>:
      <span style="color:#f92672">ospf.area</span>: <span style="color:#ae81ff">0</span>
    <span style="color:#f92672">R2</span>:
  - <span style="color:#f92672">R2</span>:
    <span style="color:#f92672">R3</span>:
      <span style="color:#f92672">ospf.area</span>: <span style="color:#ae81ff">0</span>

</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ netlab up ospf.yml
Created provider configuration file: clab.yml
Created group_vars <span style="color:#66d9ef">for</span> all
Created group_vars <span style="color:#66d9ef">for</span> eos
Created host_vars <span style="color:#66d9ef">for</span> R1
Created host_vars <span style="color:#66d9ef">for</span> R2
Created host_vars <span style="color:#66d9ef">for</span> R3
Created minimized Ansible inventory hosts.yml
Created Ansible configuration file: ansible.cfg

Step 2: Checking virtualization provider installation
<span style="color:#f92672">============================================================</span>
.. all tests succeeded, moving on

Step 3: starting the lab
<span style="color:#f92672">============================================================</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Containerlab v0.23.0 started
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Parsing &amp; checking topology file: clab.yml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating lab directory: /home/juliopdx/repos/cl-nst-demo/clab-ospf
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating docker network: Name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;clab&#39;</span>, IPv4Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;172.20.20.0/24&#39;</span>, IPv6Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;2001:172:20:20::/64&#39;</span>, MTU<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;1500&#39;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R1
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R2
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R3
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: R2:eth2 &lt;--&gt; R3:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: R1:eth1 &lt;--&gt; R2:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R3&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R2&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R1&#39;</span> node
INFO<span style="color:#f92672">[</span>0054<span style="color:#f92672">]</span> Adding containerlab host entries to /etc/hosts file
+---+--------------+--------------+--------------+------+---------+----------------+----------------------+
| <span style="color:#75715e"># |     Name     | Container ID |    Image     | Kind |  State  |  IPv4 Address  |     IPv6 Address     |</span>
+---+--------------+--------------+--------------+------+---------+----------------+----------------------+
| <span style="color:#ae81ff">1</span> | clab-ospf-R1 | ba1c4a47b6cb | ceos:4.27.2F | ceos | running | 172.20.20.3/24 | 2001:172:20:20::3/64 |
| <span style="color:#ae81ff">2</span> | clab-ospf-R2 | dbda30424bc2 | ceos:4.27.2F | ceos | running | 172.20.20.4/24 | 2001:172:20:20::4/64 |
| <span style="color:#ae81ff">3</span> | clab-ospf-R3 | 3234e3265e9c | ceos:4.27.2F | ceos | running | 172.20.20.2/24 | 2001:172:20:20::2/64 |
+---+--------------+--------------+--------------+------+---------+----------------+----------------------+

etc...
<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ docker exec -it clab-ospf-R1 Cli
R1&gt;en
R1#show ip ospf neighbor
Neighbor ID     Instance VRF      Pri State                  Dead Time   Address         Interface
10.0.0.2        <span style="color:#ae81ff">1</span>        default  <span style="color:#ae81ff">0</span>   FULL                   00:00:38    10.1.0.2        Ethernet1
R1#show ip route ospf

VRF: default

 O        10.0.0.2/32 <span style="color:#f92672">[</span>110/20<span style="color:#f92672">]</span> via 10.1.0.2, Ethernet1
 O IA     10.0.0.3/32 <span style="color:#f92672">[</span>110/30<span style="color:#f92672">]</span> via 10.1.0.2, Ethernet1
 O        10.1.0.4/30 <span style="color:#f92672">[</span>110/20<span style="color:#f92672">]</span> via 10.1.0.2, Ethernet1

R1#
</code></pre></div><h4 id="ospf-lab-for-bgp-learning">OSPF Lab for BGP Learning</h4>
<p>In this topology, the lab is predeployed with OSPF(area 0) and the students could then focus on the BGP learning.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">basico</span>
<span style="color:#f92672">provider</span>: <span style="color:#ae81ff">clab</span>
<span style="color:#f92672">defaults</span>:
  <span style="color:#f92672">device</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">devices</span>:
    <span style="color:#f92672">eos.clab.image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>


<span style="color:#f92672">addressing</span>:
  <span style="color:#f92672">loopback</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#ae81ff">200</span>::<span style="color:#ae81ff">/7</span>
  <span style="color:#f92672">router_id</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">32</span>
  <span style="color:#f92672">p2p</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.0</span><span style="color:#ae81ff">/16</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">30</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>

<span style="color:#f92672">module</span>: [<span style="color:#ae81ff">ospf]</span>

<span style="color:#f92672">nodes</span>: [<span style="color:#ae81ff">R1, R2, R3]</span>

<span style="color:#f92672">links</span>:
  - <span style="color:#ae81ff">R1-R2</span>
  - <span style="color:#ae81ff">R2-R3</span>

</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ netlab up basic_ospf.yml
Created provider configuration file: clab.yml
Created group_vars <span style="color:#66d9ef">for</span> all
Created group_vars <span style="color:#66d9ef">for</span> eos
Created host_vars <span style="color:#66d9ef">for</span> R1
Created host_vars <span style="color:#66d9ef">for</span> R2
Created host_vars <span style="color:#66d9ef">for</span> R3
Created minimized Ansible inventory hosts.yml
Created Ansible configuration file: ansible.cfg

Step 2: Checking virtualization provider installation
<span style="color:#f92672">============================================================</span>
.. all tests succeeded, moving on

Step 3: starting the lab
<span style="color:#f92672">============================================================</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Containerlab v0.23.0 started
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Parsing &amp; checking topology file: clab.yml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating lab directory: /home/juliopdx/repos/cl-nst-demo/clab-basico
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating docker network: Name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;clab&#39;</span>, IPv4Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;172.20.20.0/24&#39;</span>, IPv6Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;2001:172:20:20::/64&#39;</span>, MTU<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;1500&#39;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R2
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R1
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: R3
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: R1:eth1 &lt;--&gt; R2:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: R2:eth2 &lt;--&gt; R3:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R3&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R2&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;R1&#39;</span> node
INFO<span style="color:#f92672">[</span>0055<span style="color:#f92672">]</span> Adding containerlab host entries to /etc/hosts file
+---+----------------+--------------+--------------+------+---------+----------------+----------------------+
| <span style="color:#75715e"># |      Name      | Container ID |    Image     | Kind |  State  |  IPv4 Address  |     IPv6 Address     |</span>
+---+----------------+--------------+--------------+------+---------+----------------+----------------------+
| <span style="color:#ae81ff">1</span> | clab-basico-R1 | f079a253b1d1 | ceos:4.27.2F | ceos | running | 172.20.20.2/24 | 2001:172:20:20::2/64 |
| <span style="color:#ae81ff">2</span> | clab-basico-R2 | ed28240d8b0f | ceos:4.27.2F | ceos | running | 172.20.20.4/24 | 2001:172:20:20::4/64 |
| <span style="color:#ae81ff">3</span> | clab-basico-R3 | 0f643cc95fc7 | ceos:4.27.2F | ceos | running | 172.20.20.3/24 | 2001:172:20:20::3/64 |
+---+----------------+--------------+--------------+------+---------+----------------+----------------------+
etc...
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ docker exec -it clab-basico-R1 Cli
R1&gt;en
R1#show ip route ospf

VRF: default

 O        10.0.0.2/32 <span style="color:#f92672">[</span>110/20<span style="color:#f92672">]</span> via 10.1.0.2, Ethernet1
 O        10.0.0.3/32 <span style="color:#f92672">[</span>110/30<span style="color:#f92672">]</span> via 10.1.0.2, Ethernet1
 O        10.1.0.4/30 <span style="color:#f92672">[</span>110/20<span style="color:#f92672">]</span> via 10.1.0.2, Ethernet1

R1#
</code></pre></div><h4 id="ospf-and-bgp-lab-complete">OSPF and BGP Lab Complete</h4>
<p>In this topology, we have a complete OSPF deployment. R1 and R3 will be iBGP peers.</p>
<div class="mermaid">
    
%%{init: {'securityLevel': 'loose', 'theme':'dark'}}%%
flowchart TD
  subgraph OSPF
    subgraph AS 65000
        1((R1)) -.iBGP.- 3((R3))
    end
  1 --- 2((R2))
  3 --- 2((R2))
  end

</div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">bgp</span>
<span style="color:#f92672">provider</span>: <span style="color:#ae81ff">clab</span>
<span style="color:#f92672">defaults</span>:
  <span style="color:#f92672">device</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">devices</span>:
    <span style="color:#f92672">eos.clab.image</span>: <span style="color:#ae81ff">ceos:4.27.2F</span>


<span style="color:#f92672">addressing</span>:
  <span style="color:#f92672">loopback</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#ae81ff">200</span>::<span style="color:#ae81ff">/7</span>
  <span style="color:#f92672">router_id</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.0.0.0</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">32</span>
  <span style="color:#f92672">p2p</span>:
    <span style="color:#f92672">ipv4</span>: <span style="color:#ae81ff">10.1.0.0</span><span style="color:#ae81ff">/16</span>
    <span style="color:#f92672">prefix</span>: <span style="color:#ae81ff">30</span>
    <span style="color:#f92672">ipv6</span>: <span style="color:#66d9ef">true</span>

<span style="color:#f92672">module</span>: [<span style="color:#ae81ff">ospf]</span>

<span style="color:#f92672">nodes</span>:
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">R1</span>
    <span style="color:#f92672">module</span>: [<span style="color:#ae81ff">ospf,bgp]</span>
    <span style="color:#f92672">bgp.as</span>: <span style="color:#ae81ff">65000</span>
  - <span style="color:#ae81ff">R2</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">R3</span>
    <span style="color:#f92672">module</span>: [<span style="color:#ae81ff">ospf,bgp]</span>
    <span style="color:#f92672">bgp.as</span>: <span style="color:#ae81ff">65000</span>

<span style="color:#f92672">links</span>:
  - <span style="color:#ae81ff">R1-R2</span>
  - <span style="color:#ae81ff">R2-R3</span>

</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@containerlab:~/repos/cl-nst-demo$ docker exec -it clab-bgp-R1 Cli
R1&gt;en
R1#show ip bgp summary
BGP summary information <span style="color:#66d9ef">for</span> VRF default
Router identifier 10.0.0.1, local AS number <span style="color:#ae81ff">65000</span>
Neighbor Status Codes: m - Under maintenance
  Description              Neighbor V AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  R3                       10.0.0.3 <span style="color:#ae81ff">4</span> <span style="color:#ae81ff">65000</span>             <span style="color:#ae81ff">10</span>        <span style="color:#ae81ff">10</span>    <span style="color:#ae81ff">0</span>    <span style="color:#ae81ff">0</span> 00:05:32 Estab   <span style="color:#ae81ff">1</span>      <span style="color:#ae81ff">1</span>
R1#show ip bgp | b Network
          Network                Next Hop              Metric  AIGP       LocPref Weight  Path
 * &gt;      10.0.0.1/32            -                     -       -          -       <span style="color:#ae81ff">0</span>       i
 * &gt;      10.0.0.3/32            10.0.0.3              <span style="color:#ae81ff">0</span>       -          <span style="color:#ae81ff">100</span>     <span style="color:#ae81ff">0</span>       i
R1#
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far. I really appreciate it! I am really looking forward to using net-sim tools to create future labs. I can already see the speed to create troubleshooting labs will be increased tremendously. Thank you, Ivan and team for all the work on this tool!</p>
<ul>
<li><a href="https://unsplash.com/photos/vS7LVkPyXJU">Featured Image Philip Swinburn</a></li>
<li><a href="https://netsim-tools.readthedocs.io/en/latest/index.html">net-sim tools</a></li>
<li><a href="https://containerlab.srlinux.dev/">Containerlab</a></li>
<li><a href="https://github.com/JulioPDX/cl-nst-demo">GitHub Repository</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Learning Labs With Containerlab</title>
            <link>https://juliopdx.com/2022/01/28/learning-labs-with-containerlab/</link>
            <pubDate>Fri, 28 Jan 2022 15:45:51 -0800</pubDate>
            
            <guid>https://juliopdx.com/2022/01/28/learning-labs-with-containerlab/</guid>
            <description>Hello everyone and thank you for checking in. I’m back with another post. To be honest this one is not the norm as it will just redirect you to a project I’ve been working on with Containerlab. I’m still progressing through the Optimal Routing Design book by Cisco Press. I still haven’t finished the OSPF chapter but little by little.
A while back I mentioned that Containerlab could be an amazing resource in learning environments for individuals getting into IT or instructors trying to show students some routing design or technology involved.</description>
            <content type="html"><![CDATA[<p>Hello everyone and thank you for checking in. I’m back with another post. To be honest this one is not the norm as it will just redirect you to a project I’ve been working on with Containerlab. I’m still progressing through the Optimal Routing Design book by Cisco Press. I still haven’t finished the OSPF chapter but little by little.</p>
<p>A while back I mentioned that Containerlab could be an amazing resource in learning environments for individuals getting into IT or instructors trying to show students some routing design or technology involved. This could also lead the class to discuss design concepts or why they were chosen, possibly even collaborate with each other. For example, sharing a Containerlab file is incredibly easy. Going through this process has shown me how difficult it really is to create training content. Thank you to everyone who writes blogs, creates training videos, and helps everyone learn no matter where they are in their career.</p>
<p>I wanted to challenge myself and see if I could create something like this myself. I took a bit of inspiration from CS50X and Nick Russo’s amazing OSPF content. This is definitely not on that level or even difficulty. Currently I am on about week 7 of the CS50X course and it is seriously awesome. In that course, students build a script or a project and then automated testing is performed to validate if a project is acceptable. I created an OSPF troubleshooting lab that has a few problems and then automated testing at the end. Check it out on the GitHub link below as well as the other links shared.</p>
<p>Thank you and happy labbing/troubleshooting.</p>
<ul>
<li><a href="https://unsplash.com/photos/VyC0YSFRDTU">Featured Image by Pete Pedroza</a></li>
<li><a href="https://github.com/JulioPDX/learning_labs/tree/main/labs/ospf_tshoot/arista">OSPF Troubleshooting Lab</a></li>
<li><a href="https://github.com/nickrusso42518/ospf_brkrst3310">Nick Russo Troubleshooting OSPF (BRKRST-3310)</a></li>
<li><a href="https://cs50.harvard.edu/x/2022/">CS50X by Harvard (free)</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>EIGRP Network Design in the Lab</title>
            <link>https://juliopdx.com/2022/01/25/eigrp-network-design-in-the-lab/</link>
            <pubDate>Tue, 25 Jan 2022 15:50:47 -0800</pubDate>
            
            <guid>https://juliopdx.com/2022/01/25/eigrp-network-design-in-the-lab/</guid>
            <description>Introduction I just finished the chapter on EIGRP network design in Optimal Routing Design (Cisco Press), I wanted to provide a high level overview of what I learned and incorporated that in the lab. This book really has been great as it provides advanced routing techniques but keeps it in a maintainable format. I hope you enjoy the post and learn something along the way. I would advise you to research on your own and compare the pros and cons.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I just finished the chapter on EIGRP network design in Optimal Routing Design (Cisco Press), I wanted to provide a high level overview of what I learned and incorporated that in the lab. This book really has been great as it provides advanced routing techniques but keeps it in a maintainable format. I hope you enjoy the post and learn something along the way. I would advise you to research on your own and compare the pros and cons. Like anything, it depends.</p>
<h2 id="the-topology">The Topology</h2>
<p><img src="/blog/images/eigrp-design.png" alt="EIGRP Topo"></p>
<p>Lets break down the topology above. Everything is in EIGRP AS 12345. I just chose a random number but for the most part, using multiple autonomous systems in EIGRP does not limit the query scope, so we will use just one. Every link is configured as a /31 point to point to save on address space. We will be using a fairly traditional 3 tier design. The DMZ node is advertising a default route throughout the network and has a few loopbacks representing public IP addresses. Example of that below as well as one core nodes routing table before any optimization is performed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!dmz node redistributing a default route
router eigrp 12345
 network 10.0.0.11 0.0.0.0
 network 10.0.0.13 0.0.0.0
 redistribute static
 eigrp router-id 0.0.0.12
!
ip route 0.0.0.0 0.0.0.0 Null0
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">core3#show ip route eigrp | b Gate
Gateway of last resort is 10.0.0.13 to network 0.0.0.0

D*EX  0.0.0.0/0 [170/281600] via 10.0.0.13, 00:01:21, Ethernet0/3
      10.0.0.0/8 is variably subnetted, 54 subnets, 3 masks
D        10.0.0.0/31 [90/307200] via 10.0.0.4, 00:01:21, Ethernet0/1
                     [90/307200] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.0.0.6/31 [90/307200] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.0.0.10/31 [90/307200] via 10.0.0.13, 00:01:21, Ethernet0/3
                      [90/307200] via 10.0.0.4, 00:01:21, Ethernet0/1
D        10.0.0.14/31 [90/307200] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.0.0.16/31 [90/307200] via 10.0.0.4, 00:01:21, Ethernet0/1
D        10.1.2.0/24 [90/332800] via 10.0.0.4, 00:01:21, Ethernet0/1
                     [90/332800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.3.0/24 [90/332800] via 10.0.0.2, 00:30:01, Ethernet0/0
D        10.1.4.0/24 [90/332800] via 10.0.0.2, 00:30:01, Ethernet0/0
D        10.1.5.0/24 [90/332800] via 10.0.0.2, 00:30:01, Ethernet0/0
D        10.1.30.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.31.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.32.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.33.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.34.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.40.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.41.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.42.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.43.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.44.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.50.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.51.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.52.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.53.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.54.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.1.255.253/32 [90/435200] via 10.0.0.2, 00:30:01, Ethernet0/0
D        10.1.255.254/32 [90/435200] via 10.0.0.4, 00:01:21, Ethernet0/1
D        10.2.3.0/24 [90/332800] via 10.0.0.4, 00:01:21, Ethernet0/1
D        10.2.4.0/24 [90/332800] via 10.0.0.4, 00:01:21, Ethernet0/1
D        10.2.5.0/24 [90/332800] via 10.0.0.4, 00:01:21, Ethernet0/1
D        10.2.30.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.31.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.32.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.33.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.34.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.40.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.41.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.42.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.43.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.44.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.50.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.51.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.52.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.53.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.54.0/24 [90/460800] via 10.0.0.4, 00:01:21, Ethernet0/1
                      [90/460800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        10.2.255.253/32 [90/435200] via 10.0.0.2, 00:30:01, Ethernet0/0
D        10.2.255.254/32 [90/435200] via 10.0.0.4, 00:01:21, Ethernet0/1
      172.16.0.0/16 is variably subnetted, 15 subnets, 3 masks
D        172.16.17.0/31 [90/307200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.17.2/31 [90/307200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.17.8/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.17.16/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.17.24/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.17.32/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.17.253/32 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
                          [90/435200] via 10.0.0.2, 00:01:21, Ethernet0/0
D        172.16.17.254/32 [90/409600] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.18.0/31 [90/332800] via 10.0.0.9, 00:01:21, Ethernet0/2
                        [90/332800] via 10.0.0.2, 00:01:21, Ethernet0/0
D        172.16.18.8/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.18.16/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.18.24/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.18.32/29 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
D        172.16.18.253/32 [90/435200] via 10.0.0.9, 00:01:21, Ethernet0/2
                          [90/435200] via 10.0.0.2, 00:01:21, Ethernet0/0
D        172.16.18.254/32 [90/409600] via 10.0.0.9, 00:01:21, Ethernet0/2
core3#
</code></pre></div><p>At the moment since no optimizations have been added, if we were to lose any of these links, the network would have to ask every node if they are aware of an alternate path to reach a particular destination. Not very efficient at all. A few places where we can improve are the 10.1.0.0 and 10.2.0.0 networks. Those are all coming from nodes R3, R4, and R5. Think of these as our fake access layer in a campus. The networks in the 172.16.17.0 and 172.16.18.0 networks are coming from a services edge of the network. This could be servers or whatever services you can think of. In this case it is just one node called servers advertising some routes.</p>
<h2 id="optimizations-in-the-core-layer">Optimizations in the Core Layer</h2>
<p>The main idea behind the core is to push the traffic as fast as possible. Try to limit the extra stuffs that can consume resources within the core. Please note that I said within, do summarize from the core to the aggregation layer. How does this look like in practice? From the aggregation standpoint, all we really need from the core is a default route. One way to do this is to configure a summary on the interfaces towards the aggregates.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!core1
interface Ethernet0/2
 ip summary-address eigrp 12345 0.0.0.0 0.0.0.0
!
interface Ethernet0/3
 ip summary-address eigrp 12345 0.0.0.0 0.0.0.0
</code></pre></div><p>There’s a catch though. When you create a summary route, EIGRP will add the sweet null0 route… At this point core1 believes it has the better default route over the one the dmz node is sending. For example, lets try and ping the 8.8.8.8 network from svcs1.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">svcs1#ping 8.8.8.8
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:
U.U.U
Success rate is 0 percent (0/5)
svcs1#
</code></pre></div><p>We have effectively black holed traffic. So how do we get around this? We make that summary route look terrible so it will not be added into the routing table.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!core1-3
router eigrp 12345
 summary-metric 0.0.0.0/0 distance 255
 network 0.0.0.0
</code></pre></div><p>Please note, you will see the advertise all the things command (network 0.0.0.0) in the configurations. In reality, you should be explicit (network 10.1.2.1 0.0.0.0). Lets try that ping again!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">svcs1#ping 8.8.8.8
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/2 ms
svcs1#
</code></pre></div><p>I’ll add that config and summary metric to all core nodes pointing to their different aggregation layers!</p>
<h2 id="optimizations-in-the-aggregation-layer">Optimizations in the Aggregation Layer</h2>
<p>A bit similar as before, we can advertise summary routes from the aggregation layer to the core. The catch here is that if one of the aggregation nodes loses access to a route, it will still advertise that summary if a route within the summary is present. The solution here is to connect the aggregation nodes either physically or using a tunnel. I decided to run a direct connection for simplicity. Here is what it looks like to summarize the 10.1.0.0/16 and 10.2.0.0/16 networks from dis1 and dis2. It should be noted, the creation of the null0 route for these summaries is not a big deal, the local network will see more specific routes to reach each destination.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!dis1 and dis2
interface Ethernet1/0
 ip summary-address eigrp 12345 10.1.0.0 255.255.0.0
 ip summary-address eigrp 12345 10.2.0.0 255.255.0.0
</code></pre></div><p>Similar summaries can be performed from svcs1 and svcs2.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!svcs1 and svcs2
interface Ethernet0/0
 ip summary-address eigrp 12345 172.16.17.0 255.255.255.0
 ip summary-address eigrp 12345 172.16.18.0 255.255.255.0
</code></pre></div><p>Lets check the access layer and see how a route table is looking.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R3#  show ip route eigrp | b Gate
Gateway of last resort is 10.2.3.2 to network 0.0.0.0

D*EX  0.0.0.0/0 [170/332800] via 10.2.3.2, 00:17:27, Ethernet0/1
      10.0.0.0/8 is variably subnetted, 55 subnets, 3 masks
D        10.0.0.14/31 [90/307200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.0.0.16/31 [90/307200] via 10.2.3.2, 00:57:32, Ethernet0/1
D        10.1.2.0/24 [90/307200] via 10.2.3.2, 00:57:32, Ethernet0/1
                     [90/307200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.4.0/24 [90/307200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.5.0/24 [90/307200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.40.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.41.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.42.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.43.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.44.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.50.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.51.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.52.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.53.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.54.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.255.253/32 [90/409600] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.1.255.254/32 [90/409600] via 10.2.3.2, 00:57:32, Ethernet0/1
D        10.2.4.0/24 [90/307200] via 10.2.3.2, 00:57:32, Ethernet0/1
D        10.2.5.0/24 [90/307200] via 10.2.3.2, 00:57:32, Ethernet0/1
D        10.2.40.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.41.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.42.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.43.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.44.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.50.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.51.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.52.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.53.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.54.0/24 [90/435200] via 10.2.3.2, 00:57:32, Ethernet0/1
                      [90/435200] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.255.253/32 [90/409600] via 10.1.3.1, 00:57:32, Ethernet0/0
D        10.2.255.254/32 [90/409600] via 10.2.3.2, 00:57:32, Ethernet0/1
</code></pre></div><p>This is a bit overkill for the access layer and we can make improvements here as well. We could add another summary command and metric but lets look at another method. We will limit what is advertised to the access layer using the distribute command coupled with a prefix list. Please see example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!dis1 and dis2 pointing towards access
!Similar on svcs1 and svcs2
router eigrp 12345
 distribute-list prefix DEFAULT out Ethernet0/1
 distribute-list prefix DEFAULT out Ethernet0/2
 distribute-list prefix DEFAULT out Ethernet0/3
!
ip prefix-list DEFAULT seq 20 permit 0.0.0.0/0
</code></pre></div><p>Lets check that routing table again on R3.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R3#show ip route eigrp | b Gate
Gateway of last resort is 10.2.3.2 to network 0.0.0.0

D*EX  0.0.0.0/0 [170/332800] via 10.2.3.2, 00:34:32, Ethernet0/1
R3#
</code></pre></div><h2 id="optimizations-in-the-access-layer">Optimizations in the Access Layer</h2>
<p><img src="/blog/images/stub.png" alt="Stub"></p>
<p>The amount of filtering and summarizing performed already, we have limited the amount of queries EIGRP will send out to search for alternate paths of a route. Another addition that can be made at the access layer is setting the stub flag. Why use the stub feature? Lets say R4 had a loopback1 network of “10.1.48.1/24”. If we were to shut this interface, then we would see a query from dis1 and dis2 towards R3</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R3#debug eigrp packets query
    (QUERY)
EIGRP Packet debugging is on
R3#
*Jan 25 20:48:49.461: EIGRP: Received QUERY on Et0/1 - paklen 44 nbr 10.2.3.2
*Jan 25 20:48:49.461:   AS 12345, Flags 0x0:(NULL), Seq 2628/0 interfaceQ 0/0 iidbQ un/rely 0/0 peerQ un/rely 0/0
*Jan 25 20:48:49.462: EIGRP: Received QUERY on Et0/0 - paklen 44 nbr 10.1.3.1
*Jan 25 20:48:49.462:   AS 12345, Flags 0x0:(NULL), Seq 976/0 interfaceQ 0/0 iidbQ un/rely 0/0 peerQ un/rely 0/0
R3#u all
All possible debugging has been turned off
</code></pre></div><p>In this example R3 would not have an alternate path to this route and overall we may not want to use these access layer nodes as a transit network to reach other destinations. These devices may have lower bandwidth or overall resources. The stub feature is a neat way to say “do not query me”. Lets test this out!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!Stub setting added to all access layer nodes
R3#show run | s router
router eigrp 12345
 network 0.0.0.0
 eigrp router-id 0.0.0.6
 eigrp stub connected
R3#
R3#debug eigrp packets query
    (QUERY)
EIGRP Packet debugging is on
R3#
</code></pre></div><p>Okay that’s a bit hard to visualize in writing but test it out and you’ll see that no query is sent to R3.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">dis1# show ip eigrp neighbors detail
EIGRP-IPv4 Neighbors for AS(12345)
H   Address                 Interface              Hold Uptime   SRTT   RTO  Q  Seq
                                                   (sec)         (ms)       Cnt Num
0   10.1.3.3                Et0/1                    14 00:06:35   11   100  0  712
   Version 18.0/2.0, Retrans: 0, Retries: 0, Prefixes: 11
   Topology-ids from peer - 0
   Stub Peer Advertising (CONNECTED ) Routes
   Suppressing queries
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!dmz gets to be special because it is redistributing a static
router eigrp 12345
 network 10.0.0.11 0.0.0.0
 network 10.0.0.13 0.0.0.0
 redistribute static
 eigrp router-id 0.0.0.12
 eigrp stub static
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!Almost forgot that core3 routing table!
core3#show ip route eigrp | b Gate
Gateway of last resort is 10.0.0.13 to network 0.0.0.0

D*EX  0.0.0.0/0 [170/281600] via 10.0.0.13, 00:28:48, Ethernet0/3
      10.0.0.0/8 is variably subnetted, 15 subnets, 3 masks
D        10.0.0.0/31 [90/307200] via 10.0.0.4, 02:10:02, Ethernet0/1
                     [90/307200] via 10.0.0.2, 02:10:02, Ethernet0/0
D        10.0.0.6/31 [90/307200] via 10.0.0.2, 02:10:02, Ethernet0/0
D        10.0.0.10/31 [90/307200] via 10.0.0.4, 00:28:52, Ethernet0/1
D        10.0.0.14/31 [90/307200] via 10.0.0.2, 02:10:02, Ethernet0/0
D        10.0.0.16/31 [90/307200] via 10.0.0.4, 02:10:02, Ethernet0/1
D        10.1.0.0/16 [90/435200] via 10.0.0.4, 01:58:15, Ethernet0/1
                     [90/435200] via 10.0.0.2, 01:58:15, Ethernet0/0
D        10.2.0.0/16 [90/435200] via 10.0.0.4, 01:58:15, Ethernet0/1
                     [90/435200] via 10.0.0.2, 01:58:15, Ethernet0/0
      172.16.0.0/24 is subnetted, 2 subnets
D        172.16.17.0 [90/409600] via 10.0.0.9, 01:50:23, Ethernet0/2
D        172.16.18.0 [90/409600] via 10.0.0.9, 01:50:23, Ethernet0/2
core3#
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>I originally planned to build this using FRR but it looks like EIGRP is in Alpha stage. I will play with FRR more in the future. Due to this, the topology wont be neatly packaged in a Containerlab file. I will include the configurations below. I hope you all learned something today and thank you for stopping by. We didn’t even go into the new style of configuring EIGRP but I hope you get the point. Named EIGRP just moves some options from the interface level to the af-interface level :). Stay tuned for the next episode of dragon bal…. wait this isn’t that show! In all seriousness, I have something pretty cool planned when we get to OSPF.</p>
<ul>
<li><a href="https://unsplash.com/photos/ZSPBhokqDMc">Featured Image by Med Badr Chenmaoui</a></li>
<li><a href="/blog/files/eigrp-configs.zip">EIGRP Configurations zip</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Multipoint Redistribution and SR Linux</title>
            <link>https://juliopdx.com/2022/01/22/multipoint-redistribution-and-sr-linux/</link>
            <pubDate>Sat, 22 Jan 2022 15:25:24 -0800</pubDate>
            
            <guid>https://juliopdx.com/2022/01/22/multipoint-redistribution-and-sr-linux/</guid>
            <description>Introduction I’ve been working my way through Optimal Routing Design by Russ White, Don Slice, and Alvaro Retana. It has been a great read so far and I highly recommend it. When I work through a book I usually try and lab up any concept I can, this helps me make it stick as much as possible. Early on in the book the authors mention redistribution and possible issues that can come with doing it at more than one point in the network.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/mredis.png" alt="M REDIS"></p>
<h2 id="introduction">Introduction</h2>
<p>I’ve been working my way through Optimal Routing Design by Russ White, Don Slice, and Alvaro Retana. It has been a great read so far and I highly recommend it. When I work through a book I usually try and lab up any concept I can, this helps me make it stick as much as possible. Early on in the book the authors mention redistribution and possible issues that can come with doing it at more than one point in the network. So here we are reading this post. I hope you enjoy and possibly learn something along the way.</p>
<h2 id="doubling-up-the-learning-with-sr-linux">Doubling Up the Learning With SR Linux</h2>
<p><img src="/blog/images/convo.png" alt="Convo With Roman"></p>
<p>I’ve been itching to try out SR Linux for some time now. Roman from Nokia kept giving me the friendly nudge to try it out. well, a few days ago it was finally that day! I wont go deep into SR Linux on this post but most all of the examples will use 8 nodes running on my laptop using Containerlab. Nokia was kind enough to make their NOS container publicly available. You can pull down the image with no registrations or sacrificing an email address to spam. Thank you &lt;3.</p>
<h2 id="why-redistribute">Why Redistribute?</h2>
<p>Just in case folks reading at home are not familiar with redistribution, I’ll give a high level overview. Lets say you operate a domain that only runs OSPF, some time passes and you need to merge with another domain. Now this domain only runs EIGRP, and for some reason they don’t want to use OSPF. You could point static routes to each other if there is a clean break in IP addresses (unlikely). To make this scale or get around adding a lot of static routes, we can redistribute between OSPF and EIGRP. Essentially letting each domain advertise routes between routing protocols.</p>
<h2 id="single-point-redistribution">Single Point Redistribution</h2>
<p><img src="/blog/images/ospftoeigrp.png" alt="Single Point"></p>
<p>A while back I wrote a piece on redistributing OSPF to EIGRP, check it out <a href="https://juliopdx.com/2021/05/03/route-redistribution-ospfv3-and-eigrpv6/">here</a>. In single point redistribution, there is little chance of major issues, since there is only one entry and exit point to each domain. Where things get really interesting is when we have multipoint redistribution and that is the focus of this post.</p>
<h2 id="multipoint-redistribution">Multipoint Redistribution</h2>
<p>If we were to lose the link at the single point redistribution image above, network connectivity to the other domain would be lost. In this case we can redistribute at multiple points in the network.</p>
<h3 id="multipoint-redistribution-topology">Multipoint Redistribution Topology</h3>
<p><img src="/blog/images/mredis.png" alt="M REDIS"></p>
<p>Above we have 8 SR Linux nodes with the left portion of the network running OSPF as well as the right portion of the network. In the middle everything is running eBGP, each router has an autonomous system of 6500X, where X is the router number. R1 is advertising the network of “10.1.1.0/24” and R8 is advertising network “8.8.8.8/32”. Routers R3, R4, R6, and R7 are redistributing routes between the protocols. I added a simple filter to only advertise the network mentioned in each domain. For example, R3 and R4 will only advertise the network “10.1.1.0/24” into BGP. Example configurations of R3 are below. Please note; R3, R4, R6, and R7 have very similar policies.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">A:R3# info routing-policy
    routing-policy {
        prefix-set FILTER {
            prefix 10.1.1.0/24 mask-length-range exact {
            }
        }
        policy OSPFtoBGP {
            statement 10 {
                match {
                    prefix-set FILTER
                }
                action {
                    accept {
                    }
                }
            }
        }
        policy pass-all {
            statement 10 {
                match {
                    protocol bgp
                }
                action {
                    accept {
                    }
                }
            }
        }
    }
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">--{ running }--[ network-instance default protocols ]--
A:R3# info
    bgp {
        autonomous-system 65003
        router-id 0.0.0.3
        group EXTERNAL {
            admin-state enable
            export-policy OSPFtoBGP
            import-policy pass-all
            local-as 65003 {
            }
        }
        neighbor 10.3.5.1 {
            peer-as 65005
            peer-group EXTERNAL
        }
    }
    ospf {
        instance v2 {
            admin-state enable
            version ospf-v2
            router-id 0.0.0.3
            export-policy pass-all
            asbr {
            }
            area 0.0.0.0 {
                interface ethernet-1/1.0 {
                    interface-type point-to-point
                }
            }
        }
    }
</code></pre></div><h3 id="checking-routes-at-r5">Checking Routes at R5</h3>
<p>You probably noticed R5 is at the center of this mess, so lets check the routing table from that perspective. Remember R5 is only running BGP.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">A:R5# show network-instance default protocols bgp routes ipv4 summary
-------------------------------------------------------------------------------------------------------------------------------
Show report for the BGP route table of network-instance &#34;default&#34;
-------------------------------------------------------------------------------------------------------------------------------
Status codes: u=used, *=valid, &gt;=best, x=stale
Origin codes: i=IGP, e=EGP, ?=incomplete
-------------------------------------------------------------------------------------------------------------------------------
+-----+--------------+---------------------+-----+-----+----------------------------------------+
| Sta |   Network    |      Next Hop       | MED | Loc |                Path Val                |
| tus |              |                     |     | Pre |                                        |
|     |              |                     |     |  f  |                                        |
+=====+==============+=====================+=====+=====+========================================+
| u*&gt; | 8.8.8.8/32   | 10.5.6.1            | 16  | 100 | [65006] i                              |
| *   | 8.8.8.8/32   | 10.5.7.1            | 16  | 100 | [65007] i                              |
| u*&gt; | 10.1.1.0/24  | 10.3.5.0            | 32  | 100 | [65003] i                              |
| *   | 10.1.1.0/24  | 10.4.5.0            | 32  | 100 | [65004] i                              |
| u*&gt; | 10.3.5.0/31  | 0.0.0.0             | -   | 100 |  i                                     |
| u*&gt; | 10.4.5.0/31  | 0.0.0.0             | -   | 100 |  i                                     |
| u*&gt; | 10.5.6.0/31  | 0.0.0.0             | -   | 100 |  i                                     |
| u*&gt; | 10.5.7.0/31  | 0.0.0.0             | -   | 100 |  i                                     |
+-----+--------------+---------------------+-----+-----+----------------------------------------+
-------------------------------------------------------------------------------------------------------------------------------
8 received BGP routes: 6 used, 8 valid, 0 stale
6 available destinations: 2 with ECMP multipaths
-------------------------------------------------------------------------------------------------------------------------------
--{ running }--[  ]--
A:R5#
</code></pre></div><p>So far this looks great, R5 learned about the “8.8.8.8/32” network from R6(10.5.6.1) and R7(10.5.7.1). We can see that R5 also learned about the “10.1.1.0/24” network from R3(10.3.5.0) and R4(10.4.5.0). Lets see if R1 can reach the remote network!</p>
<h3 id="r1-reachability-to-888832">R1 Reachability to 8.8.8.8/32</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">A:R1# ping network-instance default -c 4 -I 10.1.1.1 8.8.8.8
Using network instance default
PING 8.8.8.8 (8.8.8.8) from 10.1.1.1 : 56(84) bytes of data.
From 10.2.3.1 icmp_seq=1 Time to live exceeded
From 10.2.3.1 icmp_seq=2 Time to live exceeded
From 10.2.3.1 icmp_seq=3 Time to live exceeded

--- 8.8.8.8 ping statistics ---
4 packets transmitted, 0 received, +3 errors, 100% packet loss, time 3004ms

--{ running }--[  ]--
A:R1#
</code></pre></div><p>Hmm, that doesn’t look good. Lets try a trace and maybe we can see whats going on!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">A:R1# traceroute network-instance default 8.8.8.8
Using network instance default
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.1.2.1 (10.1.2.1)  15.831 ms * *
 2  10.2.3.1 (10.2.3.1)  12.367 ms * *
 3  * * *
 4  * * *
 5  * * *
 6  * * *
 7  * * *
 8  * 10.2.3.1 (10.2.3.1)  15.266 ms *
 9  10.2.3.0 (10.2.3.0)  14.912 ms * *
10  * * *
11  * * *
12  * * *
13  * * *
14  * * *
15  * 10.2.3.0 (10.2.3.0)  12.849 ms *
16  10.2.3.1 (10.2.3.1)  18.093 ms * *
17  * * *
18  * * *
19  * * *
20  * * *
21  * * *
22  * 10.2.3.1 (10.2.3.1)  7.931 ms *
23  10.2.3.0 (10.2.3.0)  5.755 ms * *
24  * * *
25  * * *
26  * * *
27  * * *
28  * * *
29  * 10.2.3.0 (10.2.3.0)  24.179 ms *
30  10.2.3.1 (10.2.3.1)  15.856 ms * *
--{ running }--[  ]--
A:R1#
</code></pre></div><p>It seems like we reached the max hops, by default 30. At this point, we can most likely come to the conclusion that we have a routing loop. Lets check a few routes and see what they think the best path to reach the “8.8.8.8/32” network is.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text"># Nothing crazy here, R1 will send traffic towards R2(10.1.2.1)
A:R1# show network-instance default route-table ipv4-unicast prefix 8.8.8.8/32 detail
-------------------------------------------------------------------------------------------------------------------------------
IPv4 unicast route table of network instance default
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : ospfv2
Route Owner   : ospf_mgr
Metric        : 1
Preference    : 150
Best          : true
Last change   : 2022-01-22T18:50:05.915Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.1.2.1 (direct) via [ethernet-1/1.0]
-------------------------------------------------------------------------------------------------------------------------------
--{ running }--[  ]--
A:R1#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text"># Again nothing weird, R2 will go to R3(10.2.3.1) to reach this route
A:R2# show network-instance default route-table ipv4-unicast prefix 8.8.8.8/32 detail
-------------------------------------------------------------------------------------------------------------------------------
IPv4 unicast route table of network instance default
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : ospfv2
Route Owner   : ospf_mgr
Metric        : 1
Preference    : 150
Best          : true
Last change   : 2022-01-22T18:50:05.930Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.2.3.1 (direct) via [ethernet-1/2.0]
-------------------------------------------------------------------------------------------------------------------------------
--{ running }--[  ]--
A:R2#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text"># Thats odd, R3 is sending the route back to R2(10.2.3.0)
A:R3# show network-instance default route-table ipv4-unicast prefix 8.8.8.8/32 detail
-------------------------------------------------------------------------------------------------------------------------------
IPv4 unicast route table of network instance default
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : ospfv2
Route Owner   : ospf_mgr
Metric        : 1
Preference    : 150
Best          : true
Last change   : 2022-01-22T18:50:05.910Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.2.3.0 (direct) via [ethernet-1/1.0]
-------------------------------------------------------------------------------------------------------------------------------
--{ running }--[  ]--
A:R3#
</code></pre></div><p>This manual validation has shown us that the path R1 will take to reach “8.8.8.8/32” is from R1 -&gt; R2 -&gt; R3 -&gt; R2, a routing loop!</p>
<h3 id="the-problem">The problem</h3>
<p>When we have multiple points of redistribution there is a chance that loops can be introduced into the network. The reason is that routes advertised into one side of the network can be redistributed right back to our domain, possibly with a better metric. This will then have the effect of a router preferring a local path to a remote network vs using the valid path towards R5.</p>
<h3 id="the-solutions">The Solutions</h3>
<p>There are a few ways to solve this. Some of the more popular ones being filtering, route tagging, and changing administrative distances/preferences. I’ll give an example of tagging later on in this post. Lets see if we can solve this with filtering and changing the preferences first. The goal being, eBGP routes to be preferred over external OSPF routes.</p>
<h4 id="solve-with-filtering-and-preference">Solve With Filtering and Preference</h4>
<p><img src="/blog/images/mredis-allo.png" alt="Allow"></p>
<p>We mentioned before that we are only sending “10.1.1.0/24” into BGP and “8.8.8.8/32” for the other end of the network. We can add a filter to only allow the remote network from BGP into OSPF. Example of configuration below!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">A:R3# info routing-policy
    routing-policy {
        prefix-set ALLOW {
            prefix 8.8.8.8/32 mask-length-range exact {
            }
        }
        policy BGPtoOSPF {
            default-action {
                reject {
                }
            }
            statement 10 {
                match {
                    prefix-set ALLOW
                }
                action {
                    accept {
                    }
                }
            }
        }
</code></pre></div><p>We added a default action reject statement on the policy used to redistribute routes from BGP into OSPF. This will basically deny any routes not matching this policy. Similar statement was added to each router running redistribution. Lets check the connectivity from R3 and R4 standpoint.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">--{ + running }--[ network-instance default ]--
A:R3# show route-table ipv4-unicast prefix 8.8.8.8/32 detail
-------------------------------------------------------------------------------------------------------------------------------
IPv4 unicast route table of network instance default
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : ospfv2
Route Owner   : ospf_mgr
Metric        : 1
Preference    : 150
Best          : true
Last change   : 2022-01-22T21:37:23.962Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.2.3.0 (direct) via [ethernet-1/1.0]
-------------------------------------------------------------------------------------------------------------------------------
--{ + running }--[ network-instance default ]--
A:R3#
</code></pre></div><p>Interesting, R3 is preferring a route to “8.8.8.8/32” with a preference of 150 towards R2. What about R4?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">--{ + running }--[ network-instance default ]--
A:R4# show route-table ipv4-unicast prefix 8.8.8.8/32 detail
-------------------------------------------------------------------------------------------------------------------------------
IPv4 unicast route table of network instance default
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : ospfv2
Route Owner   : ospf_mgr
Metric        : 1
Preference    : 150
Best          : true
Last change   : 2022-01-22T21:37:42.270Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.2.4.0 (direct) via [ethernet-1/1.0]
-------------------------------------------------------------------------------------------------------------------------------
--{ + running }--[ network-instance default ]--
A:R4#
</code></pre></div><p>R4 is also preferring a route towards R2. At this point it seems like a race condition on who wins the route, BGP will see an internal route to reach that network and pick that over an eBGP route. Lets update the preference for external OSPF routes to be worse than 170. Why 170? Lets shutdown OSPF on R3 and see what route R4 uses to reach “8.8.8.8/32”.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">--{ + running }--[ network-instance default ]--
A:R4# show route-table ipv4-unicast prefix 8.8.8.8/32 detail
-------------------------------------------------------------------------------------------------------------------------------
IPv4 unicast route table of network instance default
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : bgp
Route Owner   : bgp_mgr
Metric        : 0
Preference    : 170
Best          : true
Last change   : 2022-01-22T21:45:53.701Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.4.5.1 (indirect) resolved by route to 10.4.5.0/31 (local)
  via 10.4.5.0 (direct) via [ethernet-1/2.0]
-------------------------------------------------------------------------------------------------------------------------------
--{ + running }--[ network-instance default ]--
A:R4#
</code></pre></div><p>We will change the external OSPF preference to be 175 and test if this allows R3 and R4 to prefer the BGP route.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">--{ +* candidate shared default }--[ network-instance default protocols ospf ]--
A:R3# info
    instance v2 {
        admin-state disable
        version ospf-v2
        router-id 0.0.0.3
        export-policy BGPtoOSPF
        external-preference 175
        asbr {
        }
        area 0.0.0.0 {
            interface ethernet-1/1.0 {
                interface-type point-to-point
            }
        }
    }
</code></pre></div><p>We can see below that R3 now prefers the BGP route to reach “8.8.8.8/32”!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">--{ + running }--[ network-instance default ]--
A:R3# show route-table ipv4-unicast prefix 8.8.8.8/32 detail
-------------------------------------------------------------------------------------------------------------------------------
IPv4 unicast route table of network instance default
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : bgp
Route Owner   : bgp_mgr
Metric        : 0
Preference    : 170
Best          : true
Last change   : 2022-01-22T21:45:52.736Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.3.5.1 (indirect) resolved by route to 10.3.5.0/31 (local)
  via 10.3.5.0 (direct) via [ethernet-1/2.0]
-------------------------------------------------------------------------------------------------------------------------------
Destination   : 8.8.8.8/32
ID            : 0
Route Type    : ospfv2
Route Owner   : ospf_mgr
Metric        : 1
Preference    : 175
Best          : false
Last change   : 2022-01-22T21:57:57.628Z
Resilient hash: false
-------------------------------------------------------------------------------------------------------------------------------
Next hops: 1 entries
10.2.3.0 (direct) via [ethernet-1/1.0]
-------------------------------------------------------------------------------------------------------------------------------
--{ + running }--[ network-instance default ]--
A:R3#
</code></pre></div><p>The rest of the redistribution nodes have been configured to match these preferences. Lets test connectivity from R1 again!</p>
<h5 id="validating-from-r1">Validating from R1</h5>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">A:R1# ping network-instance default -c 4 -I 10.1.1.1 8.8.8.8
Using network instance default
PING 8.8.8.8 (8.8.8.8) from 10.1.1.1 : 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=60 time=6.39 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=60 time=16.7 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=60 time=15.4 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=60 time=17.4 ms

--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 6.392/13.973/17.422/4.435 ms
--{ running }--[  ]--
A:R1# traceroute network-instance default  8.8.8.8
Using network instance default
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.1.2.1 (10.1.2.1)  4.861 ms * *
 2  10.2.3.1 (10.2.3.1)  4.752 ms * *
 3  10.3.5.1 (10.3.5.1)  8.857 ms * *
 4  10.5.6.1 (10.5.6.1)  8.739 ms * *
 5  8.8.8.8 (8.8.8.8)  12.996 ms  12.990 ms  12.971 ms
--{ running }--[  ]--
A:R1#
</code></pre></div><h4 id="solve-with-route-tags">Solve with Route Tags</h4>
<p>The solution above works but route tags would definitely scale more. A route tag is just a numerical value that can be assigned to routes. The strength comes in when you can query those tags to perform redistribution or policy enforcement. I could not find documentation for tagging routes using SR Linux at the time of this writing, not a knock on them I’m just very new to the NOS. In this case I’ll show some example from Cisco IOS and a high level diagram.</p>
<p><img src="/blog/images/mredis-tag.png" alt="Route Tags"></p>
<p>Bear with me, I know SR Linux does not run EIGRP :). Here we have the same topology but Cisco nodes are in place. From R3&rsquo;s perspective, when redistributing routes from OSPF to EIGRP, R3 will deny any routes tagged with 444 and set any routes not matching that to 333. In the opposite direction, routes from EIGRP to OSPF with a tag of 444 will be denied, and anything left will be tagged with 333. R4 essentially does the reverse to make sure anything from 333 is not sent to EIGRP or OSPF. Example configurations below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!R3 Config
router eigrp 34567
 default-metric 1 1 1 1 1
 network 10.3.5.0 0.0.0.0
 redistribute ospf 1 route-map filter-tags
!
router ospf 1
 router-id 0.0.0.3
 redistribute eigrp 34567 subnets route-map filter-tags
 network 10.2.3.1 0.0.0.0 area 0
!
route-map filter-tags deny 10
 match tag 444
!
route-map filter-tags permit 20
 set tag 333
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!R4 Config
router eigrp 34567
 default-metric 1 1 1 1 1
 network 10.4.5.0 0.0.0.0
 redistribute ospf 1 route-map filter-tags
!
router ospf 1
 router-id 0.0.0.4
 redistribute eigrp 34567 subnets route-map filter-tags
 network 10.2.4.1 0.0.0.0 area 0
!
route-map filter-tags deny 10
 match tag 333
!
route-map filter-tags permit 20
 set tag 444
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R5#show ip route 10.1.1.0
Routing entry for 10.1.1.0/24
  Known via &#34;eigrp 34567&#34;, distance 170, metric 2560025856
  Tag 444, type external
  Redistributing via eigrp 34567
  Last update from 10.4.5.0 on Ethernet0/1, 00:11:08 ago
  Routing Descriptor Blocks:
  * 10.4.5.0, from 10.4.5.0, 00:11:08 ago, via Ethernet0/1
      Route metric is 2560025856, traffic share count is 1
      Total delay is 1010 microseconds, minimum bandwidth is 1 Kbit
      Reliability 1/255, minimum MTU 1 bytes
      Loading 1/255, Hops 1
      Route tag 444
    10.3.5.0, from 10.3.5.0, 00:11:08 ago, via Ethernet0/0
      Route metric is 2560025856, traffic share count is 1
      Total delay is 1010 microseconds, minimum bandwidth is 1 Kbit
      Reliability 1/255, minimum MTU 1 bytes
      Loading 1/255, Hops 1
      Route tag 333
R5#
</code></pre></div><p>Here we can see the “10.1.1.0” network from each node with the appropriate tag.</p>
<h5 id="validating-from-r1-again">Validating from R1 Again</h5>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">R1#ping 8.8.8.8 source 10.1.1.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:
Packet sent with a source address of 10.1.1.1
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/3 ms
R1#traceroute 8.8.8.8 source 10.1.1.1
Type escape sequence to abort.
Tracing the route to 8.8.8.8
VRF info: (vrf in name/id, vrf out name/id)
  1 10.1.2.1 1 msec 1 msec 1 msec
  2 10.2.3.1 1 msec 0 msec 1 msec
  3 10.3.5.1 2 msec 1 msec 1 msec
  4 10.5.6.1 1 msec 2 msec 1 msec
  5 10.6.8.1 2 msec *  3 msec
R1#
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far, seriously means a lot to me. I hope you found something useful or learned a bit along the way. If I have made any errors in the writing, please let me know! The goal is definitely to be technically accurate. Another option, grab the Github repository where this lab is stored (linked below) and run a pull request! I created a Containerlab repository for this lab and probably future labs I make with Containerlab. Happy labbing and thank you for stopping by!</p>
<ul>
<li><a href="https://unsplash.com/photos/Of2rc0KOfVU">Featured Image by Pat Krupa</a></li>
<li><a href="https://github.com/JulioPDX/learning_labs/tree/main/labs/mpoint_redis">GitHub Repository</a></li>
<li><a href="https://containerlab.srlinux.dev/">Containerlab</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Learning a Little About Search Algorithms With Python</title>
            <link>https://juliopdx.com/2022/01/12/learning-a-little-about-search-algorithms-with-python/</link>
            <pubDate>Wed, 12 Jan 2022 15:12:30 -0800</pubDate>
            
            <guid>https://juliopdx.com/2022/01/12/learning-a-little-about-search-algorithms-with-python/</guid>
            <description>Introduction Hello and thank you for tuning in to another episode. First off, I was recently notified that this blog made it to the Cisco IT Blog Awards final! Pretty wild to think about, but thank you to all those involved. This one is definitely going to be different and out of my comfort zone and I appreciate you sticking with me.
I have been working through the Introduction to Computer Science course by CS50x (Harvard).</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello and thank you for tuning in to another episode. First off, I was recently notified that this blog made it to the Cisco IT Blog Awards final! Pretty wild to think about, but thank you to all those involved. This one is definitely going to be different and out of my comfort zone and I appreciate you sticking with me.</p>
<p>I have been working through the Introduction to Computer Science course by CS50x (Harvard). The course does an incredible job of walking learners through the very basics of programming with Scratch and the C language. The course eventually shifts over to Python for the remainder of the course. While taking the course, you learn about arrays, algorithms, memory, and more. David Malan and team really do an incredible job and try to make the barrier of entry as low as possible (free and includes a web based VScode editor).</p>
<p>In week three we learn a bit about algorithms. This was easily one of my favorite weeks. The class walks through different search and sorting algorithms. These are then compared performance wise. This post will go over two search algorithms; linear and binary. I will use some subnets from the 10.0.0.0/8 network for our examples.</p>
<h2 id="creating-the-data">Creating the Data</h2>
<p>I decided to create a big list of subnets with some Python. The code below will produce three files that can be used for testing.</p>
<p><code>ip_create.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#e6db74">&#34;&#34;&#34;Small script to create test files&#34;&#34;&#34;</span>
<span style="color:#f92672">import</span> ipaddress
<span style="color:#f92672">import</span> random

net <span style="color:#f92672">=</span> ipaddress<span style="color:#f92672">.</span>ip_network(<span style="color:#e6db74">&#34;10.0.0.0/8&#34;</span>)<span style="color:#f92672">.</span>subnets(new_prefix<span style="color:#f92672">=</span><span style="color:#ae81ff">24</span>)

networks <span style="color:#f92672">=</span> [str(n<span style="color:#f92672">.</span>network_address) <span style="color:#66d9ef">for</span> n <span style="color:#f92672">in</span> net]

<span style="color:#75715e"># Ordered list of subnets</span>
<span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;ordered_ip.txt&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> file:
    <span style="color:#66d9ef">for</span> net <span style="color:#f92672">in</span> networks:
        file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>net<span style="color:#e6db74">}</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)

<span style="color:#75715e"># Reversed list of subnets</span>
networks<span style="color:#f92672">.</span>reverse()
<span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;reverse_ip.txt&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> file:
    <span style="color:#66d9ef">for</span> net <span style="color:#f92672">in</span> networks:
        file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>net<span style="color:#e6db74">}</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)

<span style="color:#75715e"># Random list of subnets</span>
random<span style="color:#f92672">.</span>shuffle(networks)
<span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;random_ip.txt&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> file:
    <span style="color:#66d9ef">for</span> net <span style="color:#f92672">in</span> networks:
        file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>net<span style="color:#e6db74">}</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</code></pre></div><p><code>ordered_ip.txt</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">10.0.0.0
10.0.1.0
10.0.2.0
10.0.3.0
10.0.4.0
10.0.5.0
10.0.6.0
10.0.7.0
10.0.8.0
10.0.9.0
...
</code></pre></div><p><code>random_ip.txt</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">10.44.148.0
10.47.64.0
10.166.151.0
10.139.81.0
10.237.230.0
10.139.3.0
10.179.165.0
10.96.37.0
10.26.247.0
10.12.255.0
...
</code></pre></div><p><code>reverse_ip.txt</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">10.255.255.0
10.255.254.0
10.255.253.0
10.255.252.0
10.255.251.0
10.255.250.0
10.255.249.0
10.255.248.0
10.255.247.0
...
</code></pre></div><h2 id="linear-search">Linear Search</h2>
<p>Linear search is fairly simple in nature. We take a list or array and check one by one until we find our desired value.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">Value: <span style="color:#ae81ff">10</span> <span style="color:#ae81ff">20</span> <span style="color:#ae81ff">30</span> <span style="color:#ae81ff">40</span> <span style="color:#ae81ff">50</span> <span style="color:#ae81ff">60</span> <span style="color:#ae81ff">70</span>
Index:  <span style="color:#ae81ff">0</span>  <span style="color:#ae81ff">1</span>  <span style="color:#ae81ff">2</span>  <span style="color:#ae81ff">3</span>  <span style="color:#ae81ff">4</span>  <span style="color:#ae81ff">5</span>  <span style="color:#ae81ff">6</span>
</code></pre></div><p>For example, using the data above. If we wanted to check for the value of 70, we would first have to check 10 then 20 then 30 and so on. This means it would take us “n” steps. Think of “n” as the size of the array. In our example the array is of size 7. Worst case scenario would be 7 checks. This can then be referenced as “O(n)” or O of n. Now what about the best case scenario? Well, if we are looking for the 10 in this array, then it only requires one step. This is known as “Ω(1)” or Omega of one.</p>
<h2 id="binary-search">Binary Search</h2>
<p>Binary search works a bit differently than linear search. Binary search does have one additional requirement, the array must be sorted. If not, the algorithm breaks. Binary search works by continuously making the size of the array smaller. Essentially making the search size smaller on each iteration.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># First Check</span>
Value: <span style="color:#ae81ff">10</span> <span style="color:#ae81ff">20</span> <span style="color:#ae81ff">30</span> <span style="color:#ae81ff">40</span> <span style="color:#ae81ff">50</span> <span style="color:#ae81ff">60</span> <span style="color:#ae81ff">70</span>
Index:  <span style="color:#ae81ff">0</span>  <span style="color:#ae81ff">1</span>  <span style="color:#ae81ff">2</span>  <span style="color:#ae81ff">3</span>  <span style="color:#ae81ff">4</span>  <span style="color:#ae81ff">5</span>  <span style="color:#ae81ff">6</span>
Start: <span style="color:#ae81ff">0</span>
End: <span style="color:#ae81ff">6</span>
Midpoint: <span style="color:#ae81ff">3</span>
</code></pre></div><p>Same array as above but with additional information. We have the start of the array at 0 and the end of the array at 6. The midpoint is found by (start + end) / 2. In this case we are looking for 70. The midpoint(40) is lower than our target(70) so the start will shift to midpoint + 1.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># Second Check</span>
Value: <span style="color:#ae81ff">50</span> <span style="color:#ae81ff">60</span> <span style="color:#ae81ff">70</span>
Index:  <span style="color:#ae81ff">4</span>  <span style="color:#ae81ff">5</span>  <span style="color:#ae81ff">6</span>
Start: <span style="color:#ae81ff">4</span>
End: <span style="color:#ae81ff">6</span>
Midpoint: <span style="color:#ae81ff">5</span>
</code></pre></div><p>On the second check we are searching through a smaller piece of the array. Midpoint is found by (6 + 4) / 2 = 5. The target is higher than our midpoint of 70. So again we will shift the start to midpoint (5) + 1.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># Third Check</span>
Value: <span style="color:#ae81ff">70</span>
Index:  <span style="color:#ae81ff">6</span>
Start: <span style="color:#ae81ff">6</span>
End: <span style="color:#ae81ff">6</span>
Midpoint: <span style="color:#ae81ff">6</span>

<span style="color:#75715e"># Value is Found</span>
</code></pre></div><p>On this third check the midpoint is equal to our target, the value is found! This will also work in the opposite, but we will decrease the end to (midpoint – 1). Whats the big deal? In binary search, when the size of the array increases, the amount of steps will not grow as large as linear search. Making the algorithm much more efficient. Best case scenario, on the initial midpoint we find our target. This is similar to linear search, Ω(1). What about worst case? That is known as O(log n). Long story short, much faster.</p>
<h2 id="comparing-with-subnets">Comparing With Subnets</h2>
<p>We will be working with subnets as our data. To make things more simple, I will convert these to their decimal equivalent. What do I mean by this? Please see example below!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#f92672">from</span> ipaddress <span style="color:#f92672">import</span> ip_address
<span style="color:#f92672">&gt;&gt;&gt;</span> int(ip_address(<span style="color:#e6db74">&#34;0.0.0.1&#34;</span>))
<span style="color:#ae81ff">1</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> int(ip_address(<span style="color:#e6db74">&#34;0.0.1.0&#34;</span>))
<span style="color:#ae81ff">256</span>
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><p>When I run the comparisons, in reality they will be comparing integer values vs strings of subnets.</p>
<h3 id="linear-seach-in-code">Linear Seach in Code</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">with</span> open(sys<span style="color:#f92672">.</span>argv[<span style="color:#ae81ff">1</span>], <span style="color:#e6db74">&#34;r&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> file:
    data <span style="color:#f92672">=</span> [int(ip_address(line<span style="color:#f92672">.</span>strip())) <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> file<span style="color:#f92672">.</span>readlines()]

my_target <span style="color:#f92672">=</span> int(
    (ip_address(input(<span style="color:#e6db74">&#34;What /24 prefix in 10.0.0.0/8 are you looking for? &#34;</span>)))
)

<span style="color:#66d9ef">for</span> prefix <span style="color:#f92672">in</span> data:
    time<span style="color:#f92672">.</span>sleep(DELAY)
    <span style="color:#66d9ef">if</span> prefix <span style="color:#f92672">==</span> my_target:
        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Found: </span><span style="color:#e6db74">{</span>ip_address(my_target)<span style="color:#e6db74">}</span><span style="color:#e6db74"> with linear search&#34;</span>)
        <span style="color:#66d9ef">break</span>
</code></pre></div><p>Initially we open one of the text files and use some list comprehension to create our list of integers that represent different subnets. Then we ask the user to enter a subnet, for example, “10.80.40.0”. We will then compare each prefix or subnet against our target. If we find it, we will convert the integer back to a cleaner format. You may have caught the sleep setting in the code. Well it turns out computers are really fast, so we will add some artificial delay. This is used in both search algorithms.</p>
<h3 id="binary-search-in-code">Binary Search in Code</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">bin_search</span>(some_array: list, target: int):
    <span style="color:#e6db74">&#34;&#34;&#34;Simple binary search function&#34;&#34;&#34;</span>
    array <span style="color:#f92672">=</span> sorted(some_array)
    start <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
    end <span style="color:#f92672">=</span> len(array) <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
    found <span style="color:#f92672">=</span> <span style="color:#66d9ef">False</span>
    <span style="color:#66d9ef">while</span> <span style="color:#f92672">not</span> found <span style="color:#f92672">and</span> start <span style="color:#f92672">&lt;=</span> end:
        time<span style="color:#f92672">.</span>sleep(DELAY)
        middle <span style="color:#f92672">=</span> int((start <span style="color:#f92672">+</span> end) <span style="color:#f92672">/</span> <span style="color:#ae81ff">2</span>)
        <span style="color:#66d9ef">if</span> int(array[middle]) <span style="color:#f92672">==</span> target:
            found <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>
            print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Found: </span><span style="color:#e6db74">{</span>ip_address(target)<span style="color:#e6db74">}</span><span style="color:#e6db74"> with binary search&#34;</span>)
        <span style="color:#66d9ef">elif</span> int(array[middle]) <span style="color:#f92672">&gt;</span> target:
            end <span style="color:#f92672">=</span> middle <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
        <span style="color:#66d9ef">else</span>:
            start <span style="color:#f92672">=</span> middle <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
</code></pre></div><p>I wrapped this in a function and set it to accept two parameters, some array and a target. We will first sort the array since that is required in binary search. We then set our start and end values. Once that is done, we will run a while loop and keep making the search smaller and smaller until the target is found. Lets check out some examples!</p>
<h2 id="examples">Examples</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># Best case scenario for Linear</span>
<span style="color:#960050;background-color:#1e0010">❯</span> python algo<span style="color:#f92672">.</span>py ordered_ip<span style="color:#f92672">.</span>txt
What <span style="color:#f92672">/</span><span style="color:#ae81ff">24</span> prefix <span style="color:#f92672">in</span> <span style="color:#ae81ff">10.0.0.0</span><span style="color:#f92672">/</span><span style="color:#ae81ff">8</span> are you looking <span style="color:#66d9ef">for</span><span style="color:#960050;background-color:#1e0010">?</span> <span style="color:#ae81ff">10.0.0.0</span>
Found: <span style="color:#ae81ff">10.0.0.0</span> <span style="color:#66d9ef">with</span> linear search
Elapsed time: <span style="color:#ae81ff">0.0801</span> seconds
Found: <span style="color:#ae81ff">10.0.0.0</span> <span style="color:#66d9ef">with</span> binary search
Elapsed time: <span style="color:#ae81ff">0.0045</span> seconds
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># Worst case scenario for Linear</span>
<span style="color:#75715e"># Binary did not increase much at all!</span>
<span style="color:#960050;background-color:#1e0010">❯</span> python algo<span style="color:#f92672">.</span>py ordered_ip<span style="color:#f92672">.</span>txt
What <span style="color:#f92672">/</span><span style="color:#ae81ff">24</span> prefix <span style="color:#f92672">in</span> <span style="color:#ae81ff">10.0.0.0</span><span style="color:#f92672">/</span><span style="color:#ae81ff">8</span> are you looking <span style="color:#66d9ef">for</span><span style="color:#960050;background-color:#1e0010">?</span> <span style="color:#ae81ff">10.255.255.0</span>
Found: <span style="color:#ae81ff">10.255.255.0</span> <span style="color:#66d9ef">with</span> linear search
Elapsed time: <span style="color:#ae81ff">11.0045</span> seconds
Found: <span style="color:#ae81ff">10.255.255.0</span> <span style="color:#66d9ef">with</span> binary search
Elapsed time: <span style="color:#ae81ff">0.0050</span> seconds
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># Random list of subnets</span>
<span style="color:#960050;background-color:#1e0010">❯</span> python algo<span style="color:#f92672">.</span>py random_ip<span style="color:#f92672">.</span>txt
What <span style="color:#f92672">/</span><span style="color:#ae81ff">24</span> prefix <span style="color:#f92672">in</span> <span style="color:#ae81ff">10.0.0.0</span><span style="color:#f92672">/</span><span style="color:#ae81ff">8</span> are you looking <span style="color:#66d9ef">for</span><span style="color:#960050;background-color:#1e0010">?</span> <span style="color:#ae81ff">10.80.40.0</span>
Found: <span style="color:#ae81ff">10.80.40.0</span> <span style="color:#66d9ef">with</span> linear search
Elapsed time: <span style="color:#ae81ff">10.6104</span> seconds
Found: <span style="color:#ae81ff">10.80.40.0</span> <span style="color:#66d9ef">with</span> binary search
Elapsed time: <span style="color:#ae81ff">0.0159</span> seconds
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far, it really means a lot and please go vote for your favorite IT blogs.</p>
<ul>
<li><a href="https://unsplash.com/photos/uPXs5Vx5bIg">Featured Image by Markus Spiske</a></li>
<li><a href="https://cs50.harvard.edu/x/2022/">CS50x Introduction to Computer Science</a></li>
<li><a href="https://github.com/JulioPDX/ne-algo-search">GitHub Repository</a></li>
<li><a href="https://www.cisco.com/c/en/us/training-events/events-webinars/influencer-hub/blog-awards.html">Cisco IT Blog Awards</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>My Journey and Experience With Containerlab</title>
            <link>https://juliopdx.com/2021/12/10/my-journey-and-experience-with-containerlab/</link>
            <pubDate>Fri, 10 Dec 2021 14:32:22 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/12/10/my-journey-and-experience-with-containerlab/</guid>
            <description>Introduction Thank you all for checking out this post. This is going to be a special one. Due to the fact that I had such a joy using this new tool (new to me). I don’t like to over hype tools or new things just to hype it up. There are times where a tool or technology does deserve the credit and time in the limelight. I think Containerlabs is one of those tools.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Thank you all for checking out this post. This is going to be a special one. Due to the fact that I had such a joy using this new tool (new to me). I don’t like to over hype tools or new things just to hype it up. There are times where a tool or technology does deserve the credit and time in the limelight. I think Containerlabs is one of those tools.</p>
<h2 id="where-are-we">Where are we?</h2>
<p>Before I get started on defining Containerlab, I think its important to mention where we have come from in the networking industry. In the past when a learner or fellow engineer wanted to test a technology, design, or topology, they would have to acquire physical gear. This would take up a lot of space, power, and can you imagine the cost back in the day? I say back in the day like it was 30 years ago. More like less than 10.</p>
<p>The next phase added virtual versions of network operating systems, these could then be added to some hypervisor and stand up a virtual lab. Much more efficient but still required a lot of steps to get going and now required a server or some machine to host all the virtual machines. In the last few years, tools have sprung up to make this process even more simple (EVE-NG, GNS3, CML2).</p>
<p>I don’t want to knock these tools as I still think they are pretty incredible and provide a lot of useful features. Like anything in tech, there are trade offs. With any of these tools, there is still a requirement to have a VM or workstation available that can run said tool and then run your NOS images. For the most part these can be CPU and memory intensive, depending on the image. The process of adding the individual images can be a pain as well depending on the tool itself.</p>
<h2 id="why-containerlab">Why Containerlab?</h2>
<p>So this is where Containerlab comes in. If you read my previous CI/CD blog series, I mentioned how quick and easy it was to get applications going by just running a packaged Docker container. This could be Drone, Batfish, or Suzeiq. Imagine that simplicity but for networking images. Containerlab was created to simplify the process of creating networking topologies within a Docker environment. I’ll do my best to walk you through some of the features of Containerlab and hopefully you walk away as impressed as I was.</p>
<h2 id="getting-started-with-containerlab">Getting Started With Containerlab</h2>
<p>One of the main requirements of Containerlab is to have an environment with Linux and Docker available. In my case, I am running a Linux laptop running <a href="https://pop.system76.com/">PopOS</a>. For instructions on getting Docker installed, please see this <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04">example</a> by the team at Digital Ocean.</p>
<h3 id="installing-containerlab">Installing Containerlab</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># download and install the latest release (may require sudo)</span>
bash -c <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>curl -sL https://get-clab.srlinux.dev<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</code></pre></div><h3 id="how-to-add-networking-images-to-containerlab">How to Add Networking Images to Containerlab</h3>
<p>At this time there are a few network vendors that make acquiring container images for their NOS simple. Nokia (SR Linux) is a great example of that. In their case, you can just pull the image from a public repository to make it available to Containerlab. Others may require you to sign up for some guest account in their portal to get access to images. There are some that make you pick up the old telephone and ask for access, and others that don&rsquo;t make any of these an option. To all vendors “please make container images of your NOS and make it publicly available”. I will walk through the process of adding an Arista cEOS image to be used with Containerlab.</p>
<ul>
<li>Create <a href="https://www.arista.com/en/login">Arista</a> account, image access should be available right after verifying</li>
<li>Head to <a href="https://www.arista.com/en/support/software-download">software downloads</a></li>
</ul>
<p><img src="/blog/images/ceos-download.png" alt="cEOS Download"></p>
<p>Now that the image is downloaded, we need to make it available to Docker and Containerlab.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ docker import cEOS-lab-4.27.0F.tar ceos:4.27.0F
❯ docker images
REPOSITORY                        TAG            IMAGE ID       CREATED          SIZE
ceos                              4.27.0F        b7119ccccbcd   <span style="color:#ae81ff">27</span> seconds ago   1.68GB
</code></pre></div><h3 id="the-topology-definition-file">The Topology Definition File</h3>
<p>Now that we have a NOS container image available, we can start the initial process of standing up a lab topology. In Containerlab, this is known as the <strong>clab file</strong>. In the root on my current directory, I will create a file called <strong>net.clab.yaml</strong>. Example of my file is below. I will break down each portion in a bit.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">cicd</span>
<span style="color:#f92672">prefix</span>: <span style="color:#e6db74">&#34;&#34;</span>

<span style="color:#f92672">mgmt</span>:
  <span style="color:#f92672">network</span>: <span style="color:#ae81ff">statics</span>
  <span style="color:#f92672">ipv4_subnet</span>: <span style="color:#ae81ff">172.100.100.0</span><span style="color:#ae81ff">/24</span>

<span style="color:#f92672">topology</span>:
  <span style="color:#f92672">kinds</span>:
    <span style="color:#f92672">ceos</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.0F</span>
    <span style="color:#f92672">linux</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/hellt/network-multitool</span>
  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">pdx-rtr-eos-01</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.11</span>
    <span style="color:#f92672">pdx-rtr-eos-02</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.12</span>
    <span style="color:#f92672">pdx-rtr-eos-03</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.13</span>
    <span style="color:#f92672">pdx-rtr-eos-04</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.0F</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.14</span>
    <span style="color:#f92672">client1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.21</span>
    <span style="color:#f92672">client2</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.22</span>
  <span style="color:#f92672">links</span>:
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-eos-01:eth1&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-02:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-eos-02:eth2&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-03:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-eos-03:eth2&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-04:eth1&#34;</span>]
    <span style="color:#75715e"># Client connections</span>
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;client1:eth1&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-01:eth2&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;client2:eth1&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-04:eth2&#34;</span>]
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">name</span>: <span style="color:#ae81ff">cicd</span>
<span style="color:#f92672">prefix</span>: <span style="color:#e6db74">&#34;&#34;</span>
</code></pre></div><p>All labs must have a name at the top of the tree. This is great if you are deploying multiple labs and will prevent any conflict between them. By default lab nodes will be named using the following structure: <strong>clab-{{lab-name}}-{{node-name}}</strong>. For my example, rtr-01 would have the name of <strong>clab-cicd-pdx-rtr-eos-01</strong>. I am setting the <strong>prefix</strong> to <code>“”</code>, this will remove the prefix of <strong>clab-cicd</strong> on each node. That is more of a preference and not required.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">mgmt</span>:
  <span style="color:#f92672">network</span>: <span style="color:#ae81ff">statics</span>
  <span style="color:#f92672">ipv4_subnet</span>: <span style="color:#ae81ff">172.100.100.0</span><span style="color:#ae81ff">/24</span>
</code></pre></div><p>Here we are telling Containerlab to stand up a Docker network named <strong>statics</strong> that can then be assigned to our nodes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">topology</span>:
  <span style="color:#f92672">kinds</span>:
    <span style="color:#f92672">ceos</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.0F</span>
    <span style="color:#f92672">linux</span>:
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/hellt/network-multitool</span>
</code></pre></div><p>This is something I thought was really useful. Containerlab works by defining nodes with what kind of node it is and what image will be used for that node. The kind can be something like ceos, linux, crpd, and others. The documentation does an incredible job of breaking it all down. I will link the docs at the end of this post. In this example I am setting the kind of “ceos.image = ceos:4.27.0F”. This helps remove a lot of duplication when building out topology files with similar nodes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#f92672">nodes</span>:
    <span style="color:#f92672">pdx-rtr-eos-01</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.11</span>
    <span style="color:#f92672">pdx-rtr-eos-02</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.12</span>
    <span style="color:#f92672">pdx-rtr-eos-03</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.13</span>
    <span style="color:#f92672">pdx-rtr-eos-04</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ceos</span>
      <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ceos:4.27.0F</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.14</span>
    <span style="color:#f92672">client1</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.21</span>
    <span style="color:#f92672">client2</span>:
      <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">linux</span>
      <span style="color:#f92672">mgmt_ipv4</span>: <span style="color:#ae81ff">172.100.100.22</span>
</code></pre></div><p>This is where a lot of what has been defined is tied together. I will break down the first eos node and client node. Here, pdx-rtr-eos-01, is assigned the kind of ceos and will inherit the image of “ceos:4.27.0F”. This node will then be accessible either by IP address at 172.100.100.11 or by hostname or by the docker exec commands. I will show these examples in a bit. The client image is similar, it is a kind of linux and will inherit the image of “ghcr.io/hellt/network-multitool”, a publicly available image. If you notice I never had to pull down the image myself. It was handled by Containerlab.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#f92672">links</span>:
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-eos-01:eth1&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-02:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-eos-02:eth2&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-03:eth1&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;pdx-rtr-eos-03:eth2&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-04:eth1&#34;</span>]
    <span style="color:#75715e"># Client connections</span>
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;client1:eth1&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-01:eth2&#34;</span>]
    - <span style="color:#f92672">endpoints</span>: [<span style="color:#e6db74">&#34;client2:eth1&#34;</span>, <span style="color:#e6db74">&#34;pdx-rtr-eos-04:eth2&#34;</span>]
</code></pre></div><p>The links portion is fairly simple but it does require some planning on the front end. The link numbering is defined in the Containerlab documentation and even the examples helped me create this links format. It looks fairly simple but the magic going on behind the scenes is awesome.</p>
<h2 id="deploying-a-topology">Deploying a Topology</h2>
<p>I may have lost you at this point but please bear with me. Lets get to the fun stuff.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ time sudo containerlab deploy -t net.clab.yaml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Parsing &amp; checking topology file: net.clab.yaml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating lab directory: /home/juliopdx/git/gcl/cicd
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating docker network: Name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;statics&#39;</span>, IPv4Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;172.100.100.0/24&#39;</span>, IPv6Subnet<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;&#39;</span>, MTU<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;1500&#39;</span>
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: client1
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: client2
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: pdx-rtr-eos-04
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: pdx-rtr-eos-03
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: pdx-rtr-eos-01
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Creating container: pdx-rtr-eos-02
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: client2:eth1 &lt;--&gt; pdx-rtr-eos-04:eth2
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: pdx-rtr-eos-03:eth2 &lt;--&gt; pdx-rtr-eos-04:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: client1:eth1 &lt;--&gt; pdx-rtr-eos-01:eth2
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: pdx-rtr-eos-01:eth1 &lt;--&gt; pdx-rtr-eos-02:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Creating virtual wire: pdx-rtr-eos-02:eth2 &lt;--&gt; pdx-rtr-eos-03:eth1
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;pdx-rtr-eos-03&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;pdx-rtr-eos-04&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;pdx-rtr-eos-01&#39;</span> node
INFO<span style="color:#f92672">[</span>0001<span style="color:#f92672">]</span> Running postdeploy actions <span style="color:#66d9ef">for</span> Arista cEOS <span style="color:#e6db74">&#39;pdx-rtr-eos-02&#39;</span> node
INFO<span style="color:#f92672">[</span>0056<span style="color:#f92672">]</span> Adding containerlab host entries to /etc/hosts file
</code></pre></div><p>Lets walk through this a bit. Containerlab will spin up a directory named after our lab name, <strong>cicd</strong>. It will then create any management networks we have defined, in our case this will be the 172.100.100.0/24 network. After that each container is created and virtual links added. At the tail end you may have noticed that the Containerlab hosts were added to the <strong>/etc/hosts</strong> file. Containerlab is nice enough to provide a summary of everything that was deployed. Example below.</p>
<p><img src="/blog/images/deploy-containerlab.png" alt="Deploy Topo"></p>
<p>At the bottom you can see that it took 57 seconds to deploy the 4 cEOS containers and two Linux containers that will act as hosts. In theory we should be able to connect to the nodes?</p>
<p><img src="/blog/images/ping-container.png" alt="Ping Container"></p>
<h2 id="connecting-to-nodes">Connecting to Nodes</h2>
<p>Great, now we have everything deployed but we need to do something with it. If folks are testing or learning a NOS, naturally we want to connect to the nodes to start laying down some sweet configuration. Luckily, we have a few options to connect to each node.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ ssh admin@cicd-pdx-rtr-eos-01
Password:
pdx-rtr-eos-01&gt;en
pdx-rtr-eos-01#show version | inc image version
Software image version: 4.27.0F-24305004.4270F <span style="color:#f92672">(</span>engineering build<span style="color:#f92672">)</span>
pdx-rtr-eos-01#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ ssh admin@172.100.100.11
Password:
pdx-rtr-eos-01&gt;en
pdx-rtr-eos-01#show version | inc image version
Software image version: 4.27.0F-24305004.4270F <span style="color:#f92672">(</span>engineering build<span style="color:#f92672">)</span>
pdx-rtr-eos-01#
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ docker exec -it cicd-pdx-rtr-eos-01 Cli
pdx-rtr-eos-01&gt;en
pdx-rtr-eos-01#show version | inc image version
Software image version: 4.27.0F-24305004.4270F <span style="color:#f92672">(</span>engineering build<span style="color:#f92672">)</span>
pdx-rtr-eos-01#
</code></pre></div><h2 id="viewing-the-topology">Viewing the Topology</h2>
<p>I’m so used to using tools like GNS3 and EVE-NG. Both of these tools provide some type of GUI to view the current topology. Maybe its because I’m a visual person, but I really like to view the topology. Can you believe with a bit of digging… I found out that Containerlab can render a topology image. Example below. FYI, I don’t think the graph enjoyed all the dashes in my hostnames. I’ll post the original when I found this feature!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ sudo containerlab graph -t net.clab.yaml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Parsing &amp; checking topology file: net.clab.yaml
INFO<span style="color:#f92672">[</span>0000<span style="color:#f92672">]</span> Listening on :50080...
</code></pre></div><p><img src="/blog/images/container-graph.jpeg" alt="Container Graph"></p>
<p>Above is the original image I generated after I found this out. Seriously awesome! I would like to see the interfaces displayed though… I know I know, we had to define this in the links so who cares? I care! I want to see them in the generated image! 🙂</p>
<h2 id="automating-some-configuration">Automating Some Configuration</h2>
<p><img src="/blog/images/ci_cd_blog.png" alt="High Level Diagram"></p>
<p>A while back I wrote a series on building a CI/CD pipeline. In that series, I used a generic topology with four Arista nodes running OSPF and BGP. I wanted to see if I could recreate this topology in Containerlab and utilize some of the automation skills I have been practicing. I wont go all in on this post but I will drop some configuration with Nornir and NAPALM using the Ansible inventory Containerlab created. Oh yeah, Containerlab creates an Ansible inventory when deploying a topology. Example of the inventory that was created after I deployed this topology is below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">all</span>:
  <span style="color:#f92672">children</span>:
    <span style="color:#f92672">ceos</span>:
      <span style="color:#f92672">hosts</span>:
        <span style="color:#f92672">cicd-pdx-rtr-eos-01</span>:
          <span style="color:#f92672">ansible_host</span>: <span style="color:#ae81ff">172.100.100.11</span>
        <span style="color:#f92672">cicd-pdx-rtr-eos-02</span>:
          <span style="color:#f92672">ansible_host</span>: <span style="color:#ae81ff">172.100.100.12</span>
        <span style="color:#f92672">cicd-pdx-rtr-eos-03</span>:
          <span style="color:#f92672">ansible_host</span>: <span style="color:#ae81ff">172.100.100.13</span>
        <span style="color:#f92672">cicd-pdx-rtr-eos-04</span>:
          <span style="color:#f92672">ansible_host</span>: <span style="color:#ae81ff">172.100.100.14</span>
    <span style="color:#f92672">linux</span>:
      <span style="color:#f92672">hosts</span>:
        <span style="color:#f92672">cicd-client1</span>:
          <span style="color:#f92672">ansible_host</span>: <span style="color:#ae81ff">172.100.100.21</span>
        <span style="color:#f92672">cicd-client2</span>:
          <span style="color:#f92672">ansible_host</span>: <span style="color:#ae81ff">172.100.100.22</span>
</code></pre></div><h3 id="nornir-with-ansible-inventory">Nornir With Ansible Inventory</h3>
<p>Nornir has a plugin to interact with an Ansible inventory, after getting Nornir installed, its just a matter of installing the plugin and setting the inventory to use that plugin.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">pip install nornir nornir_ansible nornir_napalm
</code></pre></div><p><code>Nornir config.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">inventory</span>:
  <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">AnsibleInventory</span>
  <span style="color:#f92672">options</span>:
    <span style="color:#f92672">hostsfile</span>: <span style="color:#e6db74">&#34;cicd/ansible-inventory.yml&#34;</span>

<span style="color:#f92672">runner</span>:
  <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">threaded</span>
  <span style="color:#f92672">options</span>:
    <span style="color:#f92672">num_workers</span>: <span style="color:#ae81ff">10</span>
</code></pre></div><p>Below is a function used to set credentials and platform for the nodes in this deployment.</p>
<h3 id="deployment-scripts">Deployment Scripts</h3>
<p><code>tools.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#e6db74">&#34;&#34;&#34;Tools script that holds a variety of functions&#34;&#34;&#34;</span>

<span style="color:#f92672">import</span> os


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">nornir_set_creds</span>(norn, username<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;admin&#34;</span>, password<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;admin&#34;</span>, platform<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;eos&#34;</span>):
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Handler for credentials and platform
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> username:
        username <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;NORNIR_USER&#34;</span>)
    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> password:
        password <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;MY_SECRET&#34;</span>)

    <span style="color:#66d9ef">for</span> host_obj <span style="color:#f92672">in</span> norn<span style="color:#f92672">.</span>inventory<span style="color:#f92672">.</span>hosts<span style="color:#f92672">.</span>values():
        host_obj<span style="color:#f92672">.</span>username <span style="color:#f92672">=</span> username
        host_obj<span style="color:#f92672">.</span>password <span style="color:#f92672">=</span> password
        host_obj<span style="color:#f92672">.</span>platform <span style="color:#f92672">=</span> platform
</code></pre></div><p><code>deploy.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e">#!/usr/bin/env python</span>

<span style="color:#e6db74">&#34;&#34;&#34;Script used to configure the network&#34;&#34;&#34;</span>

<span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_utils.plugins.functions <span style="color:#f92672">import</span> print_result
<span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_configure
<span style="color:#f92672">from</span> tools <span style="color:#f92672">import</span> nornir_set_creds


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">deploy_network</span>(task):
    <span style="color:#e6db74">&#34;&#34;&#34;Configures network with Scrapli&#34;&#34;&#34;</span>
    <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;client&#34;</span> <span style="color:#f92672">in</span> task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name:
        <span style="color:#66d9ef">pass</span>
    <span style="color:#66d9ef">else</span>:
        task1_result <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(
            name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Configuring </span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">!&#34;</span>,
            task<span style="color:#f92672">=</span>napalm_configure,
            filename<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;configs/post/</span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">.cfg&#34;</span>,
            replace<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
        )


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Used to run all the things&#34;&#34;&#34;</span>
    norn <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;nornir_settings/config.yaml&#34;</span>)
    nornir_set_creds(norn)
    result <span style="color:#f92672">=</span> norn<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>deploy_network)
    print_result(result)


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    main()
</code></pre></div><h3 id="sample-configuration">Sample Configuration</h3>
<p><code>cicd-pdx-rtr-eos-01.cfg</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">! Command: show running-config
! device: pdx-rtr-eos-01 (cEOSLab, EOS-4.27.0F-24305004.4270F (engineering build))
!
no aaa root
!
username admin privilege 15 role network-admin secret sha512 $6$RxQ5ae0GOW6SAiCU$7qzQNGX2pSIqWIYBIYGF8Xh30lo/s418/diYEEZj9rPrTJiAkYv0s6AvjpTfUHMGz.a58Hg29Yy/nV0Zvplux0
!
transceiver qsfp default-mode 4x10G
!
service routing protocols model multi-agent
!
hostname pdx-rtr-eos-01
!
spanning-tree mode mstp
!
management api http-commands
   no shutdown
!
management api gnmi
   transport grpc default
!
management api netconf
   transport ssh default
!
interface Ethernet1
   description connection to pdx-rtr-eos-02
   no switchport
   ip address 10.0.12.1/24
   ip ospf network point-to-point
   ip ospf area 0.0.0.0
!
!
interface Ethernet2
   no switchport
   ip address 192.168.1.1/24
   ip ospf area 0.0.0.0
!
interface Loopback1
   ip address 10.0.0.1/32
   ip ospf area 0.0.0.0
!
interface Management0
   ip address 172.100.100.11/24
!
ip routing
!
router bgp 65001
   router-id 10.0.0.1
   timers bgp 10 30
   neighbor 10.0.0.4 remote-as 65004
   neighbor 10.0.0.4 update-source Loopback1
   neighbor 10.0.0.4 ebgp-multihop 3
!
router ospf 1
   router-id 10.0.0.1
   passive-interface Ethernet2
   passive-interface Loopback1
   max-lsa 12000
!
endtext
</code></pre></div><p>Script output below! I’ll set the client1 and client2 IP addresses in the meantime!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">❯ docker exec -it cicd-client1 bash
bash-5.0# ifconfig eth1 192.168.1.2 netmask 255.255.255.0 up
bash-5.0# route add default gw 192.168.1.1
bash-5.0# ping -c <span style="color:#ae81ff">4</span> 192.168.1.1
PING 192.168.1.1 <span style="color:#f92672">(</span>192.168.1.1<span style="color:#f92672">)</span> 56<span style="color:#f92672">(</span>84<span style="color:#f92672">)</span> bytes of data.
<span style="color:#ae81ff">64</span> bytes from 192.168.1.1: icmp_seq<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> ttl<span style="color:#f92672">=</span><span style="color:#ae81ff">64</span> time<span style="color:#f92672">=</span>0.064 ms
<span style="color:#ae81ff">64</span> bytes from 192.168.1.1: icmp_seq<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span> ttl<span style="color:#f92672">=</span><span style="color:#ae81ff">64</span> time<span style="color:#f92672">=</span>0.074 ms
<span style="color:#ae81ff">64</span> bytes from 192.168.1.1: icmp_seq<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span> ttl<span style="color:#f92672">=</span><span style="color:#ae81ff">64</span> time<span style="color:#f92672">=</span>0.080 ms
<span style="color:#ae81ff">64</span> bytes from 192.168.1.1: icmp_seq<span style="color:#f92672">=</span><span style="color:#ae81ff">4</span> ttl<span style="color:#f92672">=</span><span style="color:#ae81ff">64</span> time<span style="color:#f92672">=</span>0.073 ms

--- 192.168.1.1 ping statistics ---
<span style="color:#ae81ff">4</span> packets transmitted, <span style="color:#ae81ff">4</span> received, 0% packet loss, time 3072ms
rtt min/avg/max/mdev <span style="color:#f92672">=</span> 0.064/0.072/0.080/0.005 ms
bash-5.0# ifconfig eth1
eth1      Link encap:Ethernet  HWaddr AA:C1:AB:94:0D:B0
          inet addr:192.168.1.2  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::a8c1:abff:fe94:db0/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:9500  Metric:1
          RX packets:1754 errors:0 dropped:0 overruns:0 frame:0
          TX packets:26 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:218275 <span style="color:#f92672">(</span>213.1 KiB<span style="color:#f92672">)</span>  TX bytes:2112 <span style="color:#f92672">(</span>2.0 KiB<span style="color:#f92672">)</span>

bash-5.0# exit
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#960050;background-color:#1e0010">❯</span> python deploy<span style="color:#f92672">.</span>py
deploy_network<span style="color:#f92672">******************************************************************</span>
<span style="color:#f92672">*</span> cicd<span style="color:#f92672">-</span>client1 <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">**********************************************</span>
vvvv deploy_network <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">^^^^</span> END deploy_network <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> cicd<span style="color:#f92672">-</span>client2 <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">**********************************************</span>
vvvv deploy_network <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">^^^^</span> END deploy_network <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">****************************************</span>
vvvv deploy_network <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> Configuring cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span><span style="color:#960050;background-color:#1e0010">!</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">------------------------</span> INFO
<span style="color:#f92672">@@</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">22</span>,<span style="color:#ae81ff">12</span> <span style="color:#f92672">+</span><span style="color:#ae81ff">22</span>,<span style="color:#ae81ff">37</span> <span style="color:#f92672">@@</span>
    transport ssh default
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet1
<span style="color:#f92672">+</span>   description connection to pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.12.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf network point<span style="color:#f92672">-</span>to<span style="color:#f92672">-</span>point
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet2
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">192.168.1.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>interface Loopback1
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.0.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">32</span>
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Management0
    ip address <span style="color:#ae81ff">172.100.100.11</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">-</span>no ip routing
<span style="color:#f92672">+</span>ip routing
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>router bgp <span style="color:#ae81ff">65001</span>
<span style="color:#f92672">+</span>   router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">10.0.0.1</span>
<span style="color:#f92672">+</span>   timers bgp <span style="color:#ae81ff">10</span> <span style="color:#ae81ff">30</span>
<span style="color:#f92672">+</span>   neighbor <span style="color:#ae81ff">10.0.0.4</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">65004</span>
<span style="color:#f92672">+</span>   neighbor <span style="color:#ae81ff">10.0.0.4</span> update<span style="color:#f92672">-</span>source Loopback1
<span style="color:#f92672">+</span>   neighbor <span style="color:#ae81ff">10.0.0.4</span> ebgp<span style="color:#f92672">-</span>multihop <span style="color:#ae81ff">3</span>
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>router ospf <span style="color:#ae81ff">1</span>
<span style="color:#f92672">+</span>   router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">10.0.0.1</span>
<span style="color:#f92672">+</span>   passive<span style="color:#f92672">-</span>interface Ethernet2
<span style="color:#f92672">+</span>   passive<span style="color:#f92672">-</span>interface Loopback1
<span style="color:#f92672">+</span>   max<span style="color:#f92672">-</span>lsa <span style="color:#ae81ff">12000</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 end
<span style="color:#f92672">^^^^</span> END deploy_network <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">****************************************</span>
vvvv deploy_network <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> Configuring cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span><span style="color:#960050;background-color:#1e0010">!</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">------------------------</span> INFO
<span style="color:#f92672">@@</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">22</span>,<span style="color:#ae81ff">12</span> <span style="color:#f92672">+</span><span style="color:#ae81ff">22</span>,<span style="color:#ae81ff">26</span> <span style="color:#f92672">@@</span>
    transport ssh default
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet1
<span style="color:#f92672">+</span>   description connection to pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.12.2</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf network point<span style="color:#f92672">-</span>to<span style="color:#f92672">-</span>point
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet2
<span style="color:#f92672">+</span>   description connection to pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.23.2</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf network point<span style="color:#f92672">-</span>to<span style="color:#f92672">-</span>point
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Management0
    ip address <span style="color:#ae81ff">172.100.100.12</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">-</span>no ip routing
<span style="color:#f92672">+</span>ip routing
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>router ospf <span style="color:#ae81ff">1</span>
<span style="color:#f92672">+</span>   router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">10.0.0.2</span>
<span style="color:#f92672">+</span>   max<span style="color:#f92672">-</span>lsa <span style="color:#ae81ff">12000</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 end
<span style="color:#f92672">^^^^</span> END deploy_network <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">****************************************</span>
vvvv deploy_network <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> Configuring cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span><span style="color:#960050;background-color:#1e0010">!</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">------------------------</span> INFO
<span style="color:#f92672">@@</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">12</span>,<span style="color:#ae81ff">6</span> <span style="color:#f92672">+</span><span style="color:#ae81ff">12</span>,<span style="color:#ae81ff">8</span> <span style="color:#f92672">@@</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 spanning<span style="color:#f92672">-</span>tree mode mstp
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>vrf instance MGMT
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
 management api http<span style="color:#f92672">-</span>commands
    no shutdown
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">@@</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">22</span>,<span style="color:#ae81ff">12</span> <span style="color:#f92672">+</span><span style="color:#ae81ff">24</span>,<span style="color:#ae81ff">27</span> <span style="color:#f92672">@@</span>
    transport ssh default
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet1
<span style="color:#f92672">+</span>   description connection to pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.23.3</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf network point<span style="color:#f92672">-</span>to<span style="color:#f92672">-</span>point
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet2
<span style="color:#f92672">+</span>   description connection to pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.34.3</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf network point<span style="color:#f92672">-</span>to<span style="color:#f92672">-</span>point
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Management0
    ip address <span style="color:#ae81ff">172.100.100.13</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">-</span>no ip routing
<span style="color:#f92672">+</span>ip routing
<span style="color:#f92672">+</span>no ip routing vrf MGMT
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>router ospf <span style="color:#ae81ff">1</span>
<span style="color:#f92672">+</span>   router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">10.0.0.3</span>
<span style="color:#f92672">+</span>   max<span style="color:#f92672">-</span>lsa <span style="color:#ae81ff">12000</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 end
<span style="color:#f92672">^^^^</span> END deploy_network <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">****************************************</span>
vvvv deploy_network <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> Configuring cicd<span style="color:#f92672">-</span>pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span><span style="color:#960050;background-color:#1e0010">!</span> <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">------------------------</span> INFO
<span style="color:#f92672">@@</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>,<span style="color:#ae81ff">7</span> <span style="color:#f92672">+</span><span style="color:#ae81ff">2</span>,<span style="color:#ae81ff">7</span> <span style="color:#f92672">@@</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 no aaa root
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">-</span>username admin privilege <span style="color:#ae81ff">15</span> role network<span style="color:#f92672">-</span>admin secret sha512 <span style="color:#960050;background-color:#1e0010">$</span><span style="color:#ae81ff">6</span><span style="color:#960050;background-color:#1e0010">$</span><span style="color:#ae81ff">80</span>vAK4C7egYGmMCr<span style="color:#960050;background-color:#1e0010">$</span>aQs6Oe1HPzToV9KkGBQozUNUzaelo8cM6EXUzDfsjPF4q<span style="color:#f92672">/</span>LJDb3WOtP01uqrCzKJWk3KpOMno40Df1nsilfwI<span style="color:#f92672">/</span>
<span style="color:#f92672">+</span>username admin privilege <span style="color:#ae81ff">15</span> role network<span style="color:#f92672">-</span>admin secret sha512 <span style="color:#960050;background-color:#1e0010">$</span><span style="color:#ae81ff">6</span><span style="color:#960050;background-color:#1e0010">$</span>RxQ5ae0GOW6SAiCU<span style="color:#960050;background-color:#1e0010">$</span><span style="color:#ae81ff">7</span>qzQNGX2pSIqWIYBIYGF8Xh30lo<span style="color:#f92672">/</span>s418<span style="color:#f92672">/</span>diYEEZj9rPrTJiAkYv0s6AvjpTfUHMGz<span style="color:#f92672">.</span>a58Hg29Yy<span style="color:#f92672">/</span>nV0Zvplux0
 <span style="color:#960050;background-color:#1e0010">!</span>
 transceiver qsfp default<span style="color:#f92672">-</span>mode <span style="color:#ae81ff">4</span>x10G
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">@@</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">22</span>,<span style="color:#ae81ff">12</span> <span style="color:#f92672">+</span><span style="color:#ae81ff">22</span>,<span style="color:#ae81ff">37</span> <span style="color:#f92672">@@</span>
    transport ssh default
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet1
<span style="color:#f92672">+</span>   description connection to pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.34.4</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf network point<span style="color:#f92672">-</span>to<span style="color:#f92672">-</span>point
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Ethernet2
<span style="color:#f92672">+</span>   no switchport
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">192.168.4.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>interface Loopback1
<span style="color:#f92672">+</span>   ip address <span style="color:#ae81ff">10.0.0.4</span><span style="color:#f92672">/</span><span style="color:#ae81ff">32</span>
<span style="color:#f92672">+</span>   ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 interface Management0
    ip address <span style="color:#ae81ff">172.100.100.14</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">-</span>no ip routing
<span style="color:#f92672">+</span>ip routing
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>router bgp <span style="color:#ae81ff">65004</span>
<span style="color:#f92672">+</span>   router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">10.0.0.4</span>
<span style="color:#f92672">+</span>   timers bgp <span style="color:#ae81ff">10</span> <span style="color:#ae81ff">30</span>
<span style="color:#f92672">+</span>   neighbor <span style="color:#ae81ff">10.0.0.1</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">65001</span>
<span style="color:#f92672">+</span>   neighbor <span style="color:#ae81ff">10.0.0.1</span> update<span style="color:#f92672">-</span>source Loopback1
<span style="color:#f92672">+</span>   neighbor <span style="color:#ae81ff">10.0.0.1</span> ebgp<span style="color:#f92672">-</span>multihop <span style="color:#ae81ff">3</span>
<span style="color:#f92672">+</span><span style="color:#960050;background-color:#1e0010">!</span>
<span style="color:#f92672">+</span>router ospf <span style="color:#ae81ff">1</span>
<span style="color:#f92672">+</span>   router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">10.0.0.4</span>
<span style="color:#f92672">+</span>   passive<span style="color:#f92672">-</span>interface Ethernet2
<span style="color:#f92672">+</span>   passive<span style="color:#f92672">-</span>interface Loopback1
<span style="color:#f92672">+</span>   max<span style="color:#f92672">-</span>lsa <span style="color:#ae81ff">12000</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 end
<span style="color:#f92672">^^^^</span> END deploy_network <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
</code></pre></div><h3 id="simple-verifications">Simple Verifications</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">pdx-rtr-eos-01#show ip ospf neighbor
Neighbor ID     Instance VRF      Pri State                  Dead Time   Address         Interface
10.0.0.2        1        default  0   FULL                   00:00:38    10.0.12.2       Ethernet1
pdx-rtr-eos-01#show ip route ospf

 O        10.0.0.4/32 [110/40] via 10.0.12.2, Ethernet1
 O        10.0.23.0/24 [110/20] via 10.0.12.2, Ethernet1
 O        10.0.34.0/24 [110/30] via 10.0.12.2, Ethernet1
 O        192.168.4.0/24 [110/40] via 10.0.12.2, Ethernet1

pdx-rtr-eos-01#show ip bgp summary
BGP summary information for VRF default
Router identifier 10.0.0.1, local AS number 65001
Neighbor Status Codes: m - Under maintenance
  Neighbor         V AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  10.0.0.4         4 65004             86        86    0    0 00:11:34 Estab   0      0
pdx-rtr-eos-01#ping 192.168.4.1
PING 192.168.4.1 (192.168.4.1) 72(100) bytes of data.
80 bytes from 192.168.4.1: icmp_seq=1 ttl=62 time=0.164 ms
80 bytes from 192.168.4.1: icmp_seq=2 ttl=62 time=0.041 ms
80 bytes from 192.168.4.1: icmp_seq=3 ttl=62 time=0.033 ms
80 bytes from 192.168.4.1: icmp_seq=4 ttl=62 time=0.034 ms
80 bytes from 192.168.4.1: icmp_seq=5 ttl=62 time=0.033 ms

--- 192.168.4.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.033/0.061/0.164/0.051 ms, ipg/ewma 0.094/0.110 ms
pdx-rtr-eos-01#
</code></pre></div><p>At this point client1 (192.168.1.2) should be able to reach client2 (192.168.4.2)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">❯ docker exec -it cicd-client1 bash
bash-5.0# ping -c 4 192.168.4.2
PING 192.168.4.2 (192.168.4.2) 56(84) bytes of data.
64 bytes from 192.168.4.2: icmp_seq=1 ttl=60 time=0.202 ms
64 bytes from 192.168.4.2: icmp_seq=2 ttl=60 time=0.186 ms
64 bytes from 192.168.4.2: icmp_seq=3 ttl=60 time=0.215 ms
64 bytes from 192.168.4.2: icmp_seq=4 ttl=60 time=0.254 ms

--- 192.168.4.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3066ms
rtt min/avg/max/mdev = 0.186/0.214/0.254/0.025 ms
bash-5.0#
</code></pre></div><h2 id="destroying-a-topology">Destroying a Topology</h2>
<p>When destroying the lab there are two main options. Well really there is more but the other sounds dangerous! You can either destroy the lab which is more like shutting it down. Or you can destroy a lab and cleanup all directories/configurations that have been configured. The former will maintain configurations when you deploy again and the latter will revert to a factory reset if you will.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#75715e"># Maintain configurations after shutdown</span>
sudo containerlab destroy -t net.clab.yaml
<span style="color:#75715e"># Destroy all configurations</span>
sudo containerlab destroy -t net.clab.yaml --cleanup
</code></pre></div><h2 id="closing-thoughts">Closing Thoughts</h2>
<p>I think Containerlabs is pretty freaking awesome. The fact that I can deploy this topology in under a minute is crazy and just from my laptop. I can see Containerlab in so many different avenues. Architects testing out a design, Engineers doing it all, or students on their initial journey down the networking path or IT in general. One of the coolest things that comes to mind is Containerlab in a classroom settings. Just imagine the Instructor or teacher creates a topology file. At this point were assuming the class has a Linux system and Docker installed. The instructor then says “Okay students today we’ll be going over topology <code>&lt;insert awesome topology&gt;.clab.yaml</code>. Students then learn about why a design was chosen or the technologies involved.</p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all so much for reading this far. I hope this wasn’t too long. GitHub repository is linked below! I was kind of building this on the fly and the documentation for Containerlab is pretty excellent. If you have any questions seriously check it out. Most all of your questions will be answered there!</p>
<ul>
<li><a href="https://unsplash.com/photos/pwcKF7L4-no">Featured Image by Louis Reed</a></li>
<li><a href="https://containerlab.srlinux.dev/">Containerlab Documentation</a></li>
<li><a href="https://netsim-tools.readthedocs.io/en/latest/">Another Option: netsim-tools by Ivan Pepelnjak</a></li>
<li><a href="https://github.com/JulioPDX/gcl">GitHub repository for code used in this blog</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>My Journey Learning About Juniper Junos</title>
            <link>https://juliopdx.com/2021/12/06/my-journey-learning-about-juniper-junos/</link>
            <pubDate>Mon, 06 Dec 2021 13:57:09 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/12/06/my-journey-learning-about-juniper-junos/</guid>
            <description>Introduction Hello and thank you for checking out another blog post. I’ve been under the weather for a bit but I am back on the keys. This post will be all about some neat things I discovered while learning a bit about the Juniper Junos NOS (network operating system). Previously I had never used Juniper, it was a vendor that always eluded me. My previous employers did not utilize this NOS and I never got around to adding it in the lab.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello and thank you for checking out another blog post. I’ve been under the weather for a bit but I am back on the keys. This post will be all about some neat things I discovered while learning a bit about the Juniper Junos NOS (network operating system). Previously I had never used Juniper, it was a vendor that always eluded me. My previous employers did not utilize this NOS and I never got around to adding it in the lab. Sit back and enjoy, I’ll include a neat automation example at the end.</p>
<h2 id="what-is-junos">What is Junos?</h2>
<p>Junos is the main operating system used in Juniper network devices. This could be the MX (routing), QFX (switching), and SRX (firewall). Since the same NOS is used across the product line, moving between them all in a configuration feels like being in the same device. This has a lot of benefits like eliminating that mode in your head of “which CLI am I in or which version of OS am I running?”. For the most part, it doesn’t matter, one configuration setting can transfer across all devices. At this point I’m probably overselling it, I’m sure there are some differences but you get the point.</p>
<h2 id="reading-the-junos-cli">Reading the Junos CLI</h2>
<p>When I first saw Junos CLI output… I’ll be honest, the first thought in my head was “WTF is that?”. Is it JSON? Is it a weird version of JSON? Most of my past experience was on the Cisco CLI and going from Cisco to this was a bit of a surprise. Below is a snippet from a Junos configuration (modified for brevity).</p>
<p><code>Junos Config Snippet</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1&gt; show configuration
## Last commit: 2021-12-06 11:19:02 PST by juliopdx
version 18.2R1.9;
system {
    login {
        message &#34;Welcome to the world of tomorrow!&#34;;
    }
    root-authentication {
        encrypted-password &#34;$6$xUmeLj6B$NBFrTm.&#34;; ## SECRET-DATA
    }
    host-name R1;
    time-zone America/Los_Angeles;
    services {
        ssh;
    }
}
interfaces {
    ge-0/0/0 {
        unit 0 {
            family inet {
                address 10.0.12.1/24;
            }
        }
    }
}
routing-options {
    static {
        route 0.0.0.0/0 {
            next-hop 192.168.10.1;
            no-readvertise;
        }
    }
    router-id 1.1.1.1;
}
protocols {
    ospf {
        reference-bandwidth 1g;
        area 0.0.0.0 {
            interface ge-0/0/0.0;
        }
    }
}
</code></pre></div><p>Looks a bit odd right? In reality, everything is in a nice orderly place. For example, any <strong>system</strong> configurations are neatly nested under that block. Any interface based settings would be under the <strong>interfaces</strong> block. Anything for OSPF is nested under <strong>protocols ospf</strong>.</p>
<h2 id="cli-modes">CLI Modes</h2>
<p>I think I’m getting a bit ahead of myself. Lets talk about the CLI modes. Junos mainly uses two CLI modes; operational and configuration. The mode you see displayed above is operational mode. Think of this as the mode you would use to view items or perform some… wait for it… operational tasks (monitor, troubleshoot, connectivity… ping). The second mode, configuration, is used to perform any and all configuration tasks on a Junos device.</p>
<p><code>Configuration Mode</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1&gt; configure
Entering configuration mode

[edit]
juliopdx@R1# &#34;Hi this is configuration mode&#34;
</code></pre></div><p>Earlier I ran <strong>show configuration</strong> to view the configuration in operational mode. These commands can be executed in configuration mode as well by prepending <strong>run</strong> to the command. Please note, <strong>show</strong> works a bit differently when in configuration mode. Typing <strong>show</strong> will simply display the current configuration tree we are in (hold that thought for just a moment).</p>
<p><code>Using ping with run</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# run ping 10.0.23.3
PING 10.0.23.3 (10.0.23.3): 56 data bytes
64 bytes from 10.0.23.3: icmp_seq=0 ttl=64 time=331.137 ms
64 bytes from 10.0.23.3: icmp_seq=1 ttl=64 time=28.864 ms
64 bytes from 10.0.23.3: icmp_seq=2 ttl=64 time=697.261 ms
64 bytes from 10.0.23.3: icmp_seq=3 ttl=64 time=1142.495 ms
64 bytes from 10.0.23.3: icmp_seq=4 ttl=64 time=148.471 ms
^C
--- 10.0.23.3 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 28.864/469.646/1142.495/405.256 ms

[edit]
juliopdx@R1#
</code></pre></div><h2 id="configuring-junos">Configuring Junos</h2>
<p>At some point now we need to actually configure something. Let me just show you a small snippet of an interface configuration and we can walk through how the configuration goes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">interfaces {
    ge-0/0/0 {
        unit 0 {
            family inet {
                address 10.0.12.1/24;
            }
        }
    }
</code></pre></div><p>When you want to configure or remove something in Junos, the commands will start with either <strong>set</strong> or <strong>delete</strong>. If we wanted to configure interface ge-0/0/0 with IP address 10.0.12.1/24, the command below would do just that.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">set interfaces ge-0/0/0 unit 0 family inet address 10.0.12.1/24
</code></pre></div><p>If you compare the configuration output to the command, its so similar, its basically the command in a tree format. You probably saw this and thought “What the heck is unit 0 and inet?”. Well <strong>unit 0</strong> will be used to create a sub-interface on the interface. Think of this as the default sub-interface that is created or all interfaces. Kind of how there is a secret default VRF in Cisco land but its not really displayed. The <strong>inet</strong> is essentially the IPv4 address family. Can you guess what <strong>inet6</strong> is?</p>
<p>That command got a bit long and there is an option to shorten our commands. Lets say for example we were about to drop a long configuration stanza on interface ge-0/0/0 and we wanted to shorten the amount of typing. We can actually tell Junos what section of the configuration we would like to edit. Example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# edit interfaces ge-0/0/0 unit 0 family inet

[edit interfaces ge-0/0/0 unit 0 family inet]
juliopdx@R1#
</code></pre></div><p>At this point we are in configuration mode and in the ge-0/0/0 interface hierarchy. Remember when I said hold that though on the <strong>show</strong> command? If we type <strong>show</strong> now, it will only display what section we are currently on. Another example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# show
address 10.0.12.1/24;

[edit interfaces ge-0/0/0 unit 0 family inet]
juliopdx@R1
</code></pre></div><p>Now if we wanted to configure that address, the command would be much shorter.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# set address 10.0.12.1/24

[edit interfaces ge-0/0/0 unit 0 family inet]
juliopdx@R1#
</code></pre></div><h2 id="replace-compare-and-commit">Replace, Compare, and Commit</h2>
<p>I really like how Junos does this. If we were to make a change in Junos, it doesn’t immediately activate the change. It stores this in something called a candidate configuration. Lets test this. Currently interface ge-0/0/0 has an IP of 10.0.12.1, the remote side has an address of 10.0.12.2. Lets change the config and see if we can ping the remote side.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">[edit interfaces ge-0/0/0 unit 0 family inet]
juliopdx@R1# top

[edit]
juliopdx@R1# replace pattern 10.0.12.1/24 with 10.0.50.1/24

[edit]
juliopdx@R1#
</code></pre></div><p>I’ll walk through what is going on here. We entered <strong>top</strong> to return us to the highest level of the config hierarchy. We then execute another way of implementing a change, by using <strong>replace</strong>! This allows us to change a text patter with another, without stepping through the entire configuration. Lets check out the current state of our changes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# show | compare
[edit interfaces ge-0/0/0 unit 0 family inet]
+       address 10.0.50.1/24;
-       address 10.0.12.1/24;

[edit]
juliopdx@R1# run ping 10.0.12.2
PING 10.0.12.2 (10.0.12.2): 56 data bytes
64 bytes from 10.0.12.2: icmp_seq=0 ttl=64 time=269.266 ms
64 bytes from 10.0.12.2: icmp_seq=1 ttl=64 time=640.029 ms
64 bytes from 10.0.12.2: icmp_seq=2 ttl=64 time=116.563 ms
64 bytes from 10.0.12.2: icmp_seq=3 ttl=64 time=3.603 ms
^C
--- 10.0.12.2 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.603/257.365/640.029/240.205 ms

[edit]
juliopdx@R1#
</code></pre></div><p>At this point we can see that 10.0.12.1 has been replaced with 10.0.50.1 under the ge-0/0/0 interface. But we can still ping the remote side? This is because the change is only on the candidate configuration and not the running configuration. I’ll commit these changes and we should lose access to the remote side.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# commit comment &#34;This is a bad config&#34;
commit complete

[edit]
juliopdx@R1# run ping 10.0.12.2
PING 10.0.12.2 (10.0.12.2): 56 data bytes
36 bytes from 4.68.38.157: Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 0054 45fd   0 0000  3c  01 5773 192.168.10.143  10.0.12.2

36 bytes from 4.68.38.157: Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 0054 4637   0 0000  3c  01 5739 192.168.10.143  10.0.12.2

36 bytes from 4.68.38.157: Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 0054 464e   0 0000  3c  01 5722 192.168.10.143  10.0.12.2

^C
--- 10.0.12.2 ping statistics ---
5 packets transmitted, 0 packets received, 100% packet loss

[edit]
juliopdx@R1#
</code></pre></div><h2 id="rollback-and-commit-confirmed">Rollback and Commit Confirmed</h2>
<p>Clearly our change was not successful. Imagine this but at a grander scale than just my simple address change. Junos has a very simple option to go back to a previously working configuration. You may have noticed that we added a comment to our bad configuration change. Lets check out our previous commits.</p>
<p><code>Previous Commits</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">[edit]
juliopdx@R1# run show system commit
0   2021-12-06 14:32:57 PST by juliopdx via cli
    This is a bad config
1   2021-12-06 13:41:27 PST by juliopdx via netconf
</code></pre></div><p>I’m staying in configuration mode so some of these commands are prepended with <strong>run</strong>. It looks like commit 0, the latest commit is our bad commit. Commit 1 is our last known good state. Lets roll this back and see if that gets us operational.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# rollback 1
load complete

[edit]
juliopdx@R1# show interfaces ge-0/0/0
unit 0 {
    family inet {
        address 10.0.12.1/24;
    }
}

[edit]
juliopdx@R1# commit
commit complete

[edit]
juliopdx@R1# run ping 10.0.12.2
PING 10.0.12.2 (10.0.12.2): 56 data bytes
64 bytes from 10.0.12.2: icmp_seq=0 ttl=63 time=338.004 ms
64 bytes from 10.0.12.2: icmp_seq=1 ttl=63 time=8.134 ms
64 bytes from 10.0.12.2: icmp_seq=2 ttl=63 time=6.880 ms
64 bytes from 10.0.12.2: icmp_seq=3 ttl=63 time=13.623 ms
^C
--- 10.0.12.2 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 6.880/91.660/338.004/142.249 ms

[edit]
juliopdx@R1#
</code></pre></div><p>Well that was easy! Another option for safety is commit confirmed. Basically, if you run commit confirmed, you will need to run commit again to make sure you want to implement the change. This is great in case you cut yourself off from the remote device. Example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">juliopdx@R1# replace pattern 10.0.12.1/24 with 10.0.50.1/24

[edit]
juliopdx@R1# commit confirmed 1
commit confirmed will be automatically rolled back in 1 minutes unless confirmed
commit complete

# commit confirmed will be rolled back in 1 minute
[edit]
juliopdx@R1#
Broadcast Message from root@R1
        (no tty) at 14:44 PST...

Commit was not confirmed; automatic rollback complete.


[edit]
juliopdx@R1#
</code></pre></div><p>Pretty neat right? Since we did not type <strong>commit</strong> again, the change was automatically discarded.</p>
<h2 id="viewing-configuration-as-set-commands">Viewing Configuration as Set Commands</h2>
<p>There is another really neat way of viewing what has been configured on the device, <strong>display set</strong>. Imagine every <strong>set</strong> command that has been executed on the device being stored in a nice format that can be reused again. Please see snippet below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">[edit]
juliopdx@R1# run show configuration | display set
set interfaces ge-0/0/0 unit 0 family inet address 10.0.12.1/24
set interfaces ge-0/0/1 unit 0 family inet address 10.0.13.1/24
set interfaces ge-0/0/2 unit 0 family inet address 10.0.0.1/24
set interfaces fxp0 unit 0 family inet address 192.168.10.143/24
set interfaces lo0 unit 0 family inet address 1.1.1.1/32
set snmp description somedevice
set snmp location &#34;123 fake street&#34;
set snmp contact &#34;juliopdx@example.com&#34;
set snmp community juliopdx authorization read-only
set snmp trap-group test categories chassis
set snmp trap-group test categories link
set snmp trap-group test categories routing
set snmp trap-group test categories startup
set snmp trap-group test categories services
set snmp trap-group test targets 192.168.10.198
set routing-options static route 0.0.0.0/0 next-hop 192.168.10.1
set routing-options static route 0.0.0.0/0 no-readvertise
set routing-options router-id 1.1.1.1
set protocols ospf reference-bandwidth 1g
set protocols ospf area 0.0.0.0 interface ge-0/0/2.0 passive
set protocols ospf area 0.0.0.0 interface lo0.0 passive
set protocols ospf area 0.0.0.0 interface ge-0/0/0.0
set protocols ospf area 0.0.0.0 interface ge-0/0/1.0

[edit]
juliopdx@R1#
</code></pre></div><h2 id="bonus-automating-the-configuration">Bonus: Automating the Configuration</h2>
<p>I couldn’t help myself, I wanted to take a shot at automating some of the configuration during my Junos learning. I’ll keep this short and sweet but I stayed with the common tools of Nornir, NAPALM, and some neat Jinja2 templates. I have covered everything in the repository (linked below) before besides the Jinja2 templates. I’ll go over those and the script to close this one out.</p>
<h3 id="junos-configuration-hierarchy-and-jinja2">Junos Configuration Hierarchy and jinja2</h3>
<p>The Junos text output I showed you initially in this post really lends itself to being automated. Even from a text standpoint. Everything has really natural breaks in separation. I mentioned this before but if you wanted to modify interfaces, this would all be done under the interfaces level. I took that same approach and broke the Jinja templates out. We have a main <strong>base.j2</strong> template that will then call on the rest. This will then build the entire running configuration of a device.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/junos-auto$ tree templates/
templates/
├── base.j2
├── interfaces.j2
├── protocols.j2
├── routing-options.j2
├── snmp.j2
└── system.j2
</code></pre></div><p><code>base.j2</code></p>
<pre tabindex="0"><code class="language-jinja2" data-lang="jinja2">version 18.2R1.9;
{% include 'system.j2' %}

{% include 'interfaces.j2' %}

{% include 'snmp.j2' %}

{% include 'routing-options.j2' %}

{% include 'protocols.j2' %}
</code></pre><p><code>interfaces.j2</code></p>
<pre tabindex="0"><code class="language-jinja2" data-lang="jinja2">interfaces {
{% for interface in host.info.interfaces %}
    {{ interface.name }} {
        unit {{ interface.unit }} {
            family {{ interface.family }} {
                address {{ interface.address }};
            }
        }
    }
{% endfor %}
}
</code></pre><h3 id="defining-host-data">Defining Host Data</h3>
<p>I kept it simple and used YAML files to store data about each node. Please note this is a very simple example. There are definitely more ways to improve this and remove some of the duplicate information.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/junos-auto$ tree host_vars/
host_vars/
├── R1.yaml
├── R2.yaml
└── R3.yaml
</code></pre></div><p><code>R1.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">interfaces</span>:
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ge-0/0/0</span>
    <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">0</span>
    <span style="color:#f92672">family</span>: <span style="color:#ae81ff">inet</span>
    <span style="color:#f92672">address</span>: <span style="color:#ae81ff">10.0.12.1</span><span style="color:#ae81ff">/24</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ge-0/0/1</span>
    <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">0</span>
    <span style="color:#f92672">family</span>: <span style="color:#ae81ff">inet</span>
    <span style="color:#f92672">address</span>: <span style="color:#ae81ff">10.0.13.1</span><span style="color:#ae81ff">/24</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ge-0/0/2</span>
    <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">0</span>
    <span style="color:#f92672">family</span>: <span style="color:#ae81ff">inet</span>
    <span style="color:#f92672">address</span>: <span style="color:#ae81ff">10.0.0.1</span><span style="color:#ae81ff">/24</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">fxp0</span>
    <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">0</span>
    <span style="color:#f92672">family</span>: <span style="color:#ae81ff">inet</span>
    <span style="color:#f92672">address</span>: <span style="color:#ae81ff">192.168.10.143</span><span style="color:#ae81ff">/24</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">lo0</span>
    <span style="color:#f92672">unit</span>: <span style="color:#ae81ff">0</span>
    <span style="color:#f92672">family</span>: <span style="color:#ae81ff">inet</span>
    <span style="color:#f92672">address</span>: <span style="color:#ae81ff">1.1.1.1</span><span style="color:#ae81ff">/32</span>

<span style="color:#f92672">routing_options</span>:
  <span style="color:#f92672">statics</span>:
    - <span style="color:#f92672">route</span>: <span style="color:#ae81ff">0.0.0.0</span><span style="color:#ae81ff">/0</span>
      <span style="color:#f92672">next_hop</span>: <span style="color:#ae81ff">192.168.10.1</span>
      <span style="color:#f92672">no_readvertise</span>: <span style="color:#66d9ef">True</span>
  <span style="color:#f92672">router_id</span>: <span style="color:#ae81ff">1.1.1.1</span>

<span style="color:#f92672">protocols</span>:
  <span style="color:#f92672">ospf</span>:
    <span style="color:#f92672">ref_band</span>: <span style="color:#ae81ff">1g</span>
    <span style="color:#f92672">areas</span>:
      <span style="color:#f92672">0.0.0.0</span>:
        <span style="color:#f92672">interfaces</span>:
          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ge-0/0/2.0</span>
            <span style="color:#f92672">passive</span>: <span style="color:#66d9ef">True</span>
          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">lo0</span>
            <span style="color:#f92672">passive</span>: <span style="color:#66d9ef">True</span>
          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ge-0/0/0.0</span>
          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ge-0/0/1.0</span>
</code></pre></div><h3 id="nornir-and-napalm">Nornir and NAPALM</h3>
<p>The script below was a mix of work I’ve done in the past and a great video by IPvZero (linked below). In the video John, demonstrates the use of the <strong>load_yaml</strong> and <strong>template_file</strong> methods in Nornir. The former will load YAML data that will then be assigned to the current host. This data can then be used by the <strong>template_file</strong> method to create configurations files for each host.</p>
<p><code>junos_norn.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#e6db74">&#34;&#34;&#34;Script used to interfact with some Junos gear&#34;&#34;&#34;</span>
<span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_configure
<span style="color:#f92672">from</span> nornir_utils.plugins.functions <span style="color:#f92672">import</span> print_result
<span style="color:#f92672">from</span> nornir_utils.plugins.tasks.data <span style="color:#f92672">import</span> load_yaml
<span style="color:#f92672">from</span> nornir_jinja2.plugins.tasks <span style="color:#f92672">import</span> template_file


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">load_vars</span>(task):
    <span style="color:#e6db74">&#34;&#34;&#34;Loads data to be added to each host from vars files&#34;&#34;&#34;</span>
    my_vars <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>load_yaml, file<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;./host_vars/</span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#e6db74">}</span><span style="color:#e6db74">.yaml&#34;</span>)
    task<span style="color:#f92672">.</span>host[<span style="color:#e6db74">&#34;info&#34;</span>] <span style="color:#f92672">=</span> my_vars<span style="color:#f92672">.</span>result
    template_configurations(task)


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">template_configurations</span>(task):
    <span style="color:#e6db74">&#34;&#34;&#34;Builds network templates&#34;&#34;&#34;</span>
    temp <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Building device configuration&#34;</span>,
        task<span style="color:#f92672">=</span>template_file,
        path<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;templates&#34;</span>,
        template<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;base.j2&#34;</span>,
    )
    config <span style="color:#f92672">=</span> temp<span style="color:#f92672">.</span>result
    deploy_configurations(task, config)


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">deploy_configurations</span>(task, config):
    <span style="color:#e6db74">&#34;&#34;&#34;Loads device configurations&#34;&#34;&#34;</span>
    task<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Configuring </span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#e6db74">}</span><span style="color:#e6db74">!&#34;</span>,
        task<span style="color:#f92672">=</span>napalm_configure,
        configuration<span style="color:#f92672">=</span>config,
        replace<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
    )


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Used to run all the things&#34;&#34;&#34;</span>
    norn <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;configs/config.yaml&#34;</span>, core<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;raise_on_error&#34;</span>: <span style="color:#66d9ef">True</span>})
    result <span style="color:#f92672">=</span> norn<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>load_vars)
    print_result(result)


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    main()
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far. I hope you learned a bit about Junos and some automation. What I showed in this post barely scratches the surface on what Junos can do, definitely more to learn! This wasn’t created in a bubble and I will link some awesome resources I had when learning about Junos and automation. Please check it out if you get a chance. I will definitely be using Junos more in my day to day labbing and learning.</p>
<ul>
<li><a href="https://unsplash.com/@v2osk">Featured Image by @V2osk</a></li>
<li><a href="https://www.networkfuntimes.com/new-series-a-guide-to-junos-for-ios-engineers/">Junos for IOS Engineers by Chris Parker</a></li>
<li><a href="https://www.pluralsight.com/authors/rich-bibby">Juniper Networks JNCIA Course by Rich Bibby on Pluralsight</a></li>
<li><a href="https://www.youtube.com/watch?v=xKe_KxT2doY">Scrapli CFG Video by IPvZero</a></li>
<li><a href="https://github.com/JulioPDX/junos-auto">GitHub Repository</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>My Journey Learning About the Palo Alto Networks Python SDK</title>
            <link>https://juliopdx.com/2021/11/22/my-journey-learning-about-the-palo-alto-networks-python-sdk/</link>
            <pubDate>Mon, 22 Nov 2021 13:18:29 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/11/22/my-journey-learning-about-the-palo-alto-networks-python-sdk/</guid>
            <description>Introduction Hello and thank you for checking out another blog post. This one will be all about Python and Palo Alto Networks (PAN). I was recently going through a PAN Firewall course on Pluralsight by Craig Stansbury. Craig does an excellent job of walking learners through the process of administering and securing a PAN firewall. I wanted to take this opportunity to double dip and try to wrap my head around the PAN pan-os-python library.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello and thank you for checking out another blog post. This one will be all about Python and Palo Alto Networks (PAN). I was recently going through a PAN Firewall course on Pluralsight by Craig Stansbury. Craig does an excellent job of walking learners through the process of administering and securing a PAN firewall. I wanted to take this opportunity to double dip and try to wrap my head around the PAN pan-os-python library. This blog wont go into details of the course but it will go through the process of configuring the firewall using the PAN Python SDK.</p>
<h2 id="object-oriented-programming-oop">Object-Oriented Programming (OOP)</h2>
<p>I will do my best to break down OOP but in case my explanations are not clear enough, I will link a great post by the pros at Real Python. The Palo SDK uses objects very heavily in their code base. For example, if you create an instance of a firewall object, this not only creates a firewall but will also inherit attributes and methods from a parent class. Please see my router class example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Router</span>:
    pizza_box <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>

    <span style="color:#66d9ef">def</span> __init__(self, vendor, os):
        self<span style="color:#f92672">.</span>vendor <span style="color:#f92672">=</span> vendor
        self<span style="color:#f92672">.</span>os <span style="color:#f92672">=</span> os

<span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CiscoRouter</span>(Router):
    <span style="color:#66d9ef">pass</span>
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> my_router <span style="color:#f92672">=</span> CiscoRouter(<span style="color:#e6db74">&#34;Cisco&#34;</span>, <span style="color:#e6db74">&#34;IOS&#34;</span>)
<span style="color:#f92672">&gt;&gt;&gt;</span> my_router<span style="color:#f92672">.</span>vendor
<span style="color:#e6db74">&#39;Cisco&#39;</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> my_router<span style="color:#f92672">.</span>os
<span style="color:#e6db74">&#39;IOS&#39;</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> my_router<span style="color:#f92672">.</span>pizza_box
<span style="color:#66d9ef">True</span>
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><p>In the example above, we have a parent class called <strong>Router</strong> and a child of that class called <strong>CiscoRouter</strong>. We set “Cisco” and “IOS” as the vendor and os respectively. Even though we did not specify the “pizza_box” attribute in the <strong>CiscoRouter</strong> class, it was inherited from the <strong>Router</strong> parent class. Please view this as an oversimplification. There is so much more to learn in this arena and I have a long way to go.</p>
<h2 id="oop-in-the-pan-sdk">OOP in the PAN SDK</h2>
<p>The PAN SDK works much in the same way. There are main parent classes and many child classes below them that will inherit attributes and available methods. Let me show you an example straight from the pan-os-python code base. Lets look at a firewall object.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Firewall</span>(PanDevice):
    <span style="color:#e6db74">&#34;&#34;&#34;A Palo Alto Networks Firewall
</span><span style="color:#e6db74">    This object can represent a firewall physical chassis,virtual firewall, or individual vsys.
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
</code></pre></div><p>The <strong>Firewall</strong> class is actually a child class of the <strong>PanDevice</strong> class. Lets instantiate a firewall object to get us going.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> fw <span style="color:#f92672">=</span> firewall<span style="color:#f92672">.</span>Firewall(<span style="color:#e6db74">&#34;192.168.10.192&#34;</span>, <span style="color:#e6db74">&#34;admin&#34;</span>, <span style="color:#e6db74">&#34;PaloAlto123!&#34;</span>)
<span style="color:#f92672">&gt;&gt;&gt;</span> type(fw)
<span style="color:#f92672">&lt;</span><span style="color:#66d9ef">class</span> <span style="color:#960050;background-color:#1e0010">&#39;</span><span style="color:#a6e22e">panos</span><span style="color:#f92672">.</span>firewall<span style="color:#f92672">.</span>Firewall<span style="color:#e6db74">&#39;&gt;</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#f92672">from</span> rich <span style="color:#f92672">import</span> inspect
<span style="color:#f92672">&gt;&gt;&gt;</span> inspect(fw)
<span style="color:#960050;background-color:#1e0010">╭────────────────────</span> <span style="color:#f92672">&lt;</span><span style="color:#66d9ef">class</span> <span style="color:#960050;background-color:#1e0010">&#39;</span><span style="color:#a6e22e">panos</span><span style="color:#f92672">.</span>firewall<span style="color:#f92672">.</span>Firewall<span style="color:#e6db74">&#39;&gt; ─────────────────────╮</span>
<span style="color:#960050;background-color:#1e0010">│</span> A Palo Alto Networks Firewall                                              <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                                                            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span> <span style="color:#960050;background-color:#1e0010">╭────────────────────────────────────────────────────────────────────────╮</span> <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span> <span style="color:#960050;background-color:#1e0010">│</span> <span style="color:#f92672">&lt;</span>Firewall <span style="color:#e6db74">&#39;192.168.10.192&#39;</span> <span style="color:#66d9ef">None</span> at <span style="color:#ae81ff">0x7ffa0c5e5ac0</span><span style="color:#f92672">&gt;</span>                     <span style="color:#960050;background-color:#1e0010">│</span> <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span> <span style="color:#960050;background-color:#1e0010">╰────────────────────────────────────────────────────────────────────────╯</span> <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                                                            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                   api_key <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;LUFRPT1hUEZ2K0w3UGFzMHdxQVcxcVJLZ3VzS1NKNmc9… │</span>
<span style="color:#960050;background-color:#1e0010">│</span>              CHILDMETHODS <span style="color:#f92672">=</span> ()                                             <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                  children <span style="color:#f92672">=</span> []                                             <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                CHILDTYPES <span style="color:#f92672">=</span> (                                              <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.AuthenticationProfile&#39;</span>,            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.AuthenticationSequence&#39;</span>,           <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.Vsys&#39;</span>,                             <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.VsysResources&#39;</span>,                    <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.SystemSettings&#39;</span>,                   <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.LogSettingsSystem&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.LogSettingsConfig&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.PasswordProfile&#39;</span>,                  <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.Administrator&#39;</span>,                    <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.Telemetry&#39;</span>,                        <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.SnmpServerProfile&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.EmailServerProfile&#39;</span>,               <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.LdapServerProfile&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.SyslogServerProfile&#39;</span>,              <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;device.HttpServerProfile&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;ha.HighAvailability&#39;</span>,                     <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.AddressObject&#39;</span>,                   <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.AddressGroup&#39;</span>,                    <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.ServiceObject&#39;</span>,                   <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.ServiceGroup&#39;</span>,                    <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.Tag&#39;</span>,                             <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.ApplicationObject&#39;</span>,               <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.ApplicationGroup&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.ApplicationFilter&#39;</span>,               <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.ApplicationContainer&#39;</span>,            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.ScheduleObject&#39;</span>,                  <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.SecurityProfileGroup&#39;</span>,            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.CustomUrlCategory&#39;</span>,               <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.LogForwardingProfile&#39;</span>,            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.DynamicUserGroup&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.Region&#39;</span>,                          <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;objects.Edl&#39;</span>,                             <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;policies.Rulebase&#39;</span>,                       <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.EthernetInterface&#39;</span>,               <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.AggregateInterface&#39;</span>,              <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.LoopbackInterface&#39;</span>,               <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.TunnelInterface&#39;</span>,                 <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.VlanInterface&#39;</span>,                   <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.Vlan&#39;</span>,                            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.VirtualRouter&#39;</span>,                   <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.ManagementProfile&#39;</span>,               <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.VirtualWire&#39;</span>,                     <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.IkeGateway&#39;</span>,                      <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.IpsecTunnel&#39;</span>,                     <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.IpsecCryptoProfile&#39;</span>,              <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.IkeCryptoProfile&#39;</span>,                <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.GreTunnel&#39;</span>,                       <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.Dhcp&#39;</span>,                            <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                                 <span style="color:#e6db74">&#39;network.Zone&#39;</span>                             <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">│</span>                             )                                              <span style="color:#960050;background-color:#1e0010">│</span>
<span style="color:#960050;background-color:#1e0010">╰────────────────────────────────────────────────────────────────────────────╯</span>
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><p>Think of those <strong>CHILDTYPES</strong> as things you can associate with this firewall object. For now lets take a quick detour and look at the <strong>PanDevice</strong> class.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">PanDevice</span>(PanObject):
    <span style="color:#e6db74">&#34;&#34;&#34;A Palo Alto Networks device
</span><span style="color:#e6db74">    The device can be of any type (currently supported devices are firewall, or panorama). The class handles common device functions that apply to all device types.
</span><span style="color:#e6db74">    Usually this class is not instantiated directly. It is the base class for a firewall.Firewall object or a panorama.Panorama object.
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    NAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;hostname&#34;</span>

    <span style="color:#66d9ef">def</span> __init__(
        self,
        hostname,
        api_username<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>,
        api_password<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>,
        api_key<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>,
        port<span style="color:#f92672">=</span><span style="color:#ae81ff">443</span>,
        is_virtual<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>,
        timeout<span style="color:#f92672">=</span><span style="color:#ae81ff">1200</span>,
        interval<span style="color:#f92672">=</span><span style="color:#ae81ff">0.5</span>,
        <span style="color:#f92672">*</span>args,
        <span style="color:#f92672">**</span>kwargs
    )
</code></pre></div><p>At this point even <strong>PanDevice</strong> is a child of another class, <strong>PanObject</strong>. When we created our firewall object we did not specify a timeout. Lets check the firewall and see if this was inherited.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> fw<span style="color:#f92672">.</span>timeout
<span style="color:#ae81ff">1200</span>
</code></pre></div><p>Lets take one more detour and look at the <strong>PanObject</strong> class.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">PanObject</span>(object):
    <span style="color:#e6db74">&#34;&#34;&#34;Base class for all package objects
</span><span style="color:#e6db74">    This class defines an object that can be placed in a tree to generate configuration.&#34;&#34;&#34;</span>
</code></pre></div><p>It looks like <strong>PanObject</strong> is the base for all objects. Now you might be asking… why do I care? When configuring a PAN firewall, almost everything has some type of reference or dependency for something else to exist. For example, I cant assign an interface to a virtual router named “DMZ” if that does not exist first. Another example, I cant assign address objects to an address group if the address objects don’t exist. Once I wrapped my head around the concept of inheritance and configuration trees, the SDK made much more sense.</p>
<p><code>From pan-os-python readthedocs</code></p>
<p><img src="/blog/images/inheritance-diagram.png" alt="Inheritance"></p>
<h2 id="configuring-zone-example">Configuring Zone Example</h2>
<p>I love providing examples and its one of the best ways I learn. I’ll walk you through configuring a firewall zone and the configuration tree involved to get the correct sequences down. For reference below is an example configuration tree from the official pan-os-python documentation.</p>
<p><img src="/blog/images/zone-build.png" alt="Zone Build"></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#f92672">from</span> panos <span style="color:#f92672">import</span> firewall
<span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#f92672">from</span> panos <span style="color:#f92672">import</span> network
<span style="color:#f92672">&gt;&gt;&gt;</span> fw <span style="color:#f92672">=</span> firewall<span style="color:#f92672">.</span>Firewall(<span style="color:#e6db74">&#34;192.168.10.192&#34;</span>, <span style="color:#e6db74">&#34;admin&#34;</span>, <span style="color:#e6db74">&#34;PaloAlto123!&#34;</span>)
<span style="color:#f92672">&gt;&gt;&gt;</span> lan_zone <span style="color:#f92672">=</span> network<span style="color:#f92672">.</span>Zone(
<span style="color:#f92672">...</span>     name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;User LAN&#34;</span>,
<span style="color:#f92672">...</span>     mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;layer3&#34;</span>,
<span style="color:#f92672">...</span>     zone_profile<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;DefaultZoneProtectionProfile&#34;</span>,
<span style="color:#f92672">...</span>     log_setting<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Zone-Forwarding-Profile&#34;</span>,
<span style="color:#f92672">...</span>     enable_packet_buffer_protection<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
<span style="color:#f92672">...</span> )
<span style="color:#f92672">&gt;&gt;&gt;</span> fw<span style="color:#f92672">.</span>add(lan_zone)
<span style="color:#f92672">&lt;</span>Zone User LAN <span style="color:#ae81ff">0x7ffa0b8aa7f0</span><span style="color:#f92672">&gt;</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> lan_zone<span style="color:#f92672">.</span>create()
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><p>Lets break that all down. We are importing a few required libraries from panos. One will be used to instantiate a firewall object and the other will be used to build a zone object. We then define any required parameters to the zone object. This is then added to the <strong>fw</strong> object using the <strong>add</strong> method. There are many methods available, for this proof of concept I heavily used the <strong>create</strong> method.</p>
<h3 id="repeating-myself">Repeating Myself</h3>
<p>Once I had the process down to configure zones, interfaces, or anything else available in the SDK, I started to get everything on paper. Eventually the zone portion of my script looked something like below…</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># Creating Zones on Firewall</span>
lan_zone <span style="color:#f92672">=</span> network<span style="color:#f92672">.</span>Zone(
    name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;User LAN&#34;</span>,
    mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;layer3&#34;</span>,
    zone_profile<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;DefaultZoneProtectionProfile&#34;</span>,
    log_setting<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Zone-Forwarding-Profile&#34;</span>,
    enable_packet_buffer_protection<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
)
fw<span style="color:#f92672">.</span>add(lan_zone)
lan_zone<span style="color:#f92672">.</span>create()


dc_zone <span style="color:#f92672">=</span> network<span style="color:#f92672">.</span>Zone(
    name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Datacenter&#34;</span>,
    mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;layer3&#34;</span>,
    zone_profile<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;DefaultZoneProtectionProfile&#34;</span>,
    log_setting<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Zone-Forwarding-Profile&#34;</span>,
    enable_packet_buffer_protection<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
)
fw<span style="color:#f92672">.</span>add(dc_zone)
dc_zone<span style="color:#f92672">.</span>create()


dmz_zone <span style="color:#f92672">=</span> network<span style="color:#f92672">.</span>Zone(
    name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;DMZ&#34;</span>,
    mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;layer3&#34;</span>,
    zone_profile<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;DefaultZoneProtectionProfile&#34;</span>,
    log_setting<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Zone-Forwarding-Profile&#34;</span>,
    enable_packet_buffer_protection<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
)
fw<span style="color:#f92672">.</span>add(dmz_zone)
dmz_zone<span style="color:#f92672">.</span>create()


outside_zone <span style="color:#f92672">=</span> network<span style="color:#f92672">.</span>Zone(
    name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Outside&#34;</span>,
    mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;layer3&#34;</span>,
    zone_profile<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;OutsideZoneProtectionProfile&#34;</span>,
    log_setting<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Zone-Forwarding-Profile&#34;</span>,
    enable_packet_buffer_protection<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
)
fw<span style="color:#f92672">.</span>add(outside_zone)
outside_zone<span style="color:#f92672">.</span>create()
</code></pre></div><p>For every zone, the steps required to create them is all the same. This is true for other pieces of the firewall configuration; interfaces, tags, etc. Eventually my configuration script grew rather large and had repetition all over the place. I left that file (connect.py) in the original repository so individuals could see how something can be transformed to be a bit more manageable.</p>
<h3 id="making-zones-and-not-repeating-myself">Making Zones and Not Repeating Myself</h3>
<p>I eventually turned all of those steps into a simple function. The function would take two to three parameters. Usually something like the firewall object and the data required to create a zone.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> configs.zones <span style="color:#f92672">import</span> zones

<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">create_zone</span>(fire, curent_zone):
    <span style="color:#e6db74">&#34;&#34;&#34;Creates firewall zone and returns object&#34;&#34;&#34;</span>
    set_zone <span style="color:#f92672">=</span> network<span style="color:#f92672">.</span>Zone(<span style="color:#f92672">**</span>curent_zone)
    fire<span style="color:#f92672">.</span>add(set_zone)
    set_zone<span style="color:#f92672">.</span>create()
    <span style="color:#66d9ef">return</span> set_zone


<span style="color:#66d9ef">for</span> zone <span style="color:#f92672">in</span> zones:
    create_zone(fw, zone)
</code></pre></div><p>We now have a new import, zones! I figured it was more manageable to break out the parameters into individual Python files. An example of the directory and zones.py file is below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/pan-auto$ tree configs/
configs/
├── address_groups.py
├── address_objects.py
├── __init__.py
├── interfaces.py
├── nats.py
├── routing.py
├── security_policies.py
├── tags.py
└── zones.py
</code></pre></div><p><code>zones.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">zones <span style="color:#f92672">=</span> [
    {
        <span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;User LAN&#34;</span>,
        <span style="color:#e6db74">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;layer3&#34;</span>,
        <span style="color:#e6db74">&#34;enable_packet_buffer_protection&#34;</span>: <span style="color:#66d9ef">True</span>,
    },
    {
        <span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Datacenter&#34;</span>,
        <span style="color:#e6db74">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;layer3&#34;</span>,
        <span style="color:#e6db74">&#34;enable_packet_buffer_protection&#34;</span>: <span style="color:#66d9ef">True</span>,
    },
    {
        <span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;DMZ&#34;</span>,
        <span style="color:#e6db74">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;layer3&#34;</span>,
        <span style="color:#e6db74">&#34;enable_packet_buffer_protection&#34;</span>: <span style="color:#66d9ef">True</span>,
    },
    {
        <span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Outside&#34;</span>,
        <span style="color:#e6db74">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;layer3&#34;</span>,
        <span style="color:#e6db74">&#34;enable_packet_buffer_protection&#34;</span>: <span style="color:#66d9ef">True</span>,
    },
    {
        <span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Test&#34;</span>,
        <span style="color:#e6db74">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;layer3&#34;</span>,
        <span style="color:#e6db74">&#34;enable_packet_buffer_protection&#34;</span>: <span style="color:#66d9ef">True</span>,
    },
]
</code></pre></div><p>In the previous example you may have seen the line below and been a bit confused (I was).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">set_zone <span style="color:#f92672">=</span> network<span style="color:#f92672">.</span>Zone(<span style="color:#f92672">**</span>curent_zone)
</code></pre></div><p>What does “**” mean??? In Python this is a known as Kwargs or keyword arguments. This allows us to pass in the dictionaries we created to our object call and reference the correct parameters. This has the benefit of decoupling the object from the data completely. Every zone can have as many or as little keyword arguments as necessary to create the object.</p>
<h2 id="making-prints-pretty">Making Prints Pretty</h2>
<p><img src="/blog/images/pretty-firewall.png" alt="Pretty Firewall"></p>
<p>I’ve been sharing terminal output like this on twitter during this learning journey. This isn’t required by any means but if I’m on the terminal all the time, why not make it look good? I’ll break down one example of these tasks, the rest follow a very similar structure.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> rich.progress <span style="color:#f92672">import</span> Progress, SpinnerColumn, BarColumn, TextColumn

<span style="color:#66d9ef">with</span> Progress(
    SpinnerColumn(<span style="color:#e6db74">&#34;bouncingBall&#34;</span>, speed<span style="color:#f92672">=</span><span style="color:#ae81ff">0.6</span>),
    BarColumn(),
    TextColumn(<span style="color:#e6db74">&#34;[progress.percentage]</span><span style="color:#e6db74">{task.description}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">{task.percentage:&gt;3.0f}</span><span style="color:#e6db74">%&#34;</span>),
) <span style="color:#66d9ef">as</span> progress:

    job1 <span style="color:#f92672">=</span> progress<span style="color:#f92672">.</span>add_task(<span style="color:#e6db74">&#34;[bright_green]Configuring Zones&#34;</span>, total<span style="color:#f92672">=</span>len(zones))

    <span style="color:#66d9ef">while</span> <span style="color:#f92672">not</span> progress<span style="color:#f92672">.</span>finished:
        <span style="color:#66d9ef">for</span> zone <span style="color:#f92672">in</span> zones:
            create_zone(fw, zone)
            progress<span style="color:#f92672">.</span>update(job1, advance<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)
</code></pre></div><p>This portion of the script will use the Rich Python Library from Will McGugan, linked below! I’m importing anything required to build the progress bar output. This example was based off of the included example under Rich. I select a type of spinner to display and what text will be shown. I then add a task to <code>progress</code>. In this case <code>job1</code> has a total equal to length of the zones variable. We have five zones so this will equate to five. Once the for loop starts, it will advance the task up by one every time. Eventually this will hit 5 and <code>progress</code> will be finished.</p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this post. Really means a lot and I hope you found something in here useful. Initially working with this SDK was a pain in the rear, but after digging into the configuration trees and inheritance it all clicked. Looking through some source code never hurt either! Please note, what’s included here and in the repository linked below is just a subset of what is possible using these tools. I wish you all the best and happy automating!</p>
<ul>
<li><a href="https://unsplash.com/photos/GEyXGTY2e9w">Featured Image by Brandon Green</a></li>
<li><a href="https://www.pluralsight.com/courses/deploy-administer-secure-palo-alto-firewalls">PAN Pluralsight Course by Craig Stansbury</a></li>
<li><a href="https://pan-os-python.readthedocs.io/en/latest/readme.html">PAN-OS Python SDK Documentation</a></li>
<li><a href="https://realpython.com/python3-object-oriented-programming/#what-is-object-oriented-programming-in-python">Object-Oriented Programming (OOP) in Python 3 by Real Python/David Amos</a></li>
<li><a href="https://rich.readthedocs.io/en/stable/introduction.html">Rich Python Library by Will McGugan</a></li>
<li><a href="https://github.com/JulioPDX/pan-auto">GitHub Repository</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Building a Network CI/CD Pipeline Part 6</title>
            <link>https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/</link>
            <pubDate>Fri, 12 Nov 2021 12:26:21 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/</guid>
            <description>Introduction Hello all and thank you for tuning in to the last part of the network CI/CD series. It really means a lot and I hope you have learned a bit along the way. In this post we will focus on testing after a change is made, more specifically, we will be looking at Suzieq. If you stick around until the end, we will look at maintaining a “golden state” of configuration using cron and a message from Rocky.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/frederick-marschall.jpg" alt="Featured Image"></p>
<h2 id="introduction">Introduction</h2>
<p>Hello all and thank you for tuning in to the last part of the network CI/CD series. It really means a lot and I hope you have learned a bit along the way. In this post we will focus on testing after a change is made, more specifically, we will be looking at Suzieq. If you stick around until the end, we will look at maintaining a “golden state” of configuration using cron and a message from Rocky.</p>
<p><img src="/blog/images/ci_cd_blog.png" alt="High Level Design"></p>
<h2 id="what-is-suzieq">What is Suzieq?</h2>
<p>Suzieq allows operators and network engineers the ability to gather data about their network using an agentless model. This data is then stored in a data format known as Parquet. Once this multi-vendor data is normalized, we can begin to ask the network a numerous amount of questions. This could be things from MTU mismatches, OSPF neighbor relationship state, and many more.</p>
<h2 id="batfish-and-suzieq">Batfish and Suzieq</h2>
<p>At this point you may be wondering if this is similar to Batfish. Batfish uses a “snapshot” of the network or a view of a change we are proposing to the network. Batfish will then build a model around this snapshot to infer if any issues are present with the configuration. There are no actual connections performed to network devices using Batfish. Suzieq on the other hand uses a Poller to continuously connect to our devices and gather network state. This data can then be used to run checks or see how our network is configured and performing. I think Batfish lends itself really well to performing precheck validation. Suzieq can perform precheck validation but for this example we will focus on post change testing. I will link a great write up by the maintainers of these respective tools at the end of this blog.</p>
<h2 id="getting-suzieq-up-and-running">Getting Suzieq Up and Running</h2>
<p>We wont stray far from actions that have been performed on previous posts… that’s right, more docker! Using the docker instance of Suzieq really provides the fastest way to get going. I’ll start at the root of our Ubuntu server that is running our docker containers.</p>
<p><code>Docker Container</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx@drone:~$ docker ps
CONTAINER ID   IMAGE                         COMMAND                  NAMES
e5d807183f32   drone/drone-runner-docker:1   <span style="color:#e6db74">&#34;/bin/drone-runner-d…&#34;</span>   runner
89b442422f25   drone/drone:2                 <span style="color:#e6db74">&#34;/bin/drone-server&#34;</span>      drone
2df0b3ee02fb   batfish/batfish               <span style="color:#e6db74">&#34;java -XX:-UseCompre…&#34;</span>   batfish
juliopdx@drone:~$
</code></pre></div><h3 id="building-the-inventory-file">Building the Inventory File</h3>
<p>At the moment Suzieq supports a built in yaml format as well as using a standard yaml Ansible inventory. For this scenario I kept things simple and used the built in format. Please note, there is word from the developers to add more dynamic inventory plugins, using Netbox for example. Below is the inventory I created for this deployment, more examples can be found in the Suzieq documentation. Please note, there will be examples of old school(<code>0.15.0</code>) commands and new formats introduced in version <code>0.16.0</code>. If you are using a different version, please check out their <a href="https://suzieq.readthedocs.io/en/latest/">documentation</a>.</p>
<p><code>Version 0.15.0 inv.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">- <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">hosts</span>:
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.121 username=suzie password=suzie</span>
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.122 username=suzie password=suzie</span>
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.123 username=suzie password=suzie</span>
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.124 username=suzie password=suzie</span>
</code></pre></div><p><code>Version 0.16.0 inv.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">sources</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">eos-source</span>
  <span style="color:#f92672">type</span>: <span style="color:#ae81ff">native</span>
  <span style="color:#f92672">hosts</span>:
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.121</span>
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.122</span>
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.123</span>
    - <span style="color:#f92672">url</span>: <span style="color:#ae81ff">ssh://192.168.10.124</span>

<span style="color:#f92672">devices</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">eos-devices</span>
  <span style="color:#f92672">devtype</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">ignore-known-hosts</span>: <span style="color:#66d9ef">true</span>

<span style="color:#f92672">auths</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">suzie-user</span>
  <span style="color:#f92672">username</span>: <span style="color:#ae81ff">suzie</span>
  <span style="color:#f92672">password</span>: <span style="color:#ae81ff">plain:suzie</span>

<span style="color:#f92672">namespaces</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">eos</span>
  <span style="color:#f92672">source</span>: <span style="color:#ae81ff">eos-source</span>
  <span style="color:#f92672">device</span>: <span style="color:#ae81ff">eos-devices</span>
  <span style="color:#f92672">auth</span>: <span style="color:#ae81ff">suzie-user</span>

</code></pre></div><h3 id="installing-the-docker-containerpoller">Installing the Docker Container/Poller</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker run -d --restart always -itd <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>-v /home/juliopdx/suz/parquet-out:/suzieq/parquet <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>-v /home/juliopdx/inv.yaml:/suzieq/inv.yaml <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>--name sq-poller netenglabs/suzieq:latest
</code></pre></div><p>The command above is doing a few things. It will create a link to a local “/suz/parquet-out” directory and copy the “inv.yaml” file we created over to the docker container.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx@drone:~$ docker ps
CONTAINER ID   IMAGE                         COMMAND                  NAMES
a091d6fe3b07   netenglabs/suzieq:latest      <span style="color:#e6db74">&#34;/bin/bash&#34;</span>              sq-poller
e5d807183f32   drone/drone-runner-docker:1   <span style="color:#e6db74">&#34;/bin/drone-runner-d…&#34;</span>   runner
89b442422f25   drone/drone:2                 <span style="color:#e6db74">&#34;/bin/drone-server&#34;</span>      drone
2df0b3ee02fb   batfish/batfish               <span style="color:#e6db74">&#34;java -XX:-UseCompre…&#34;</span>   batfish
juliopdx@drone:~$
</code></pre></div><p>Now we will attach to the container and get the service running. To disconnect from the Poller and leave the service running, type “CTRL + p, CTRL + q”</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx@drone:~$ docker attach sq-poller
root@a091d6fe3b07:/suzieq# sq-poller -D inv.yaml -k <span style="color:#75715e"># version 0.15.0</span>
root@eceb70607520:/suzieq# sq-poller -I inv.yaml <span style="color:#75715e"># version 0.16.0+</span>
</code></pre></div><h3 id="side-note-the-suzieq-cli">Side Note, The Suzieq CLI</h3>
<p>In my case I will be interacting with Suzieq using their Python library and the Poller, but you can interact with Suzieq using a CLI or GUI as well. For the moment, the Poller has most likely gathered some data about our network. Lets stop the Poller(CTRL + c) and connect to the CLI!</p>
<p><img src="/blog/images/suziecli.png" alt="Suzie CLI"></p>
<p>From the CLI we can quickly explore a ton of information about our network and run some asserts to see if our routing protocols are working as expected. As an example below are some working routing neighbor relationships and an example when one is down(I added a duplicate router ID).</p>
<p><code>Assert Pass</code></p>
<p><img src="/blog/images/routing-assert.png" alt="Assert Pass"></p>
<p><code>Assert Fail</code></p>
<p><img src="/blog/images/routingassertfailed.png" alt="Assert Fail"></p>
<p>I think you get the idea. The Suzieq CLI is a quick way to explore not only the capabilities of Suzieq but also your network. At this point lets exit the CLI, restart the Poller, and exit from the container(CTRL + p, CTRL + q).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">root&gt; exit
root@a091d6fe3b07:/suzieq# sq-poller -D inv.yaml -k <span style="color:#75715e"># version 0.15.0</span>
root@eceb70607520:/suzieq# sq-poller -I inv.yaml <span style="color:#75715e"># version 0.16.0+</span>
read escape sequence
juliopdx@drone:~$
</code></pre></div><h3 id="directory-permissions">Directory Permissions</h3>
<p>You may not run into this but in my case I ran into an issue where I had to give more permissions to the directory on my host machine running the containers.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo chmod -R a+rwx suz/
</code></pre></div><h2 id="the-challenge-getting-the-data-to-the-pipeline-runner">The Challenge: Getting the Data to the Pipeline Runner</h2>
<p>The Poller collects a wide range of data from our network devices, to get an idea of what is being collected, check out the tree breakdown below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx@drone:~$ tree -d -L <span style="color:#ae81ff">3</span> suz
suz
└── parquet-out
    ├── arpnd
    │   └── sqvers<span style="color:#f92672">=</span>1.0
    ├── bgp
    │   └── sqvers<span style="color:#f92672">=</span>2.0
    ├── coalesced
    │   ├── arpnd
    │   ├── bgp
    │   ├── devconfig
    │   ├── device
    │   ├── ifCounters
    │   ├── interfaces
    │   ├── lldp
    │   ├── ospfIf
    │   ├── ospfNbr
    │   ├── routes
    │   ├── sqCoalescer
    │   ├── sqPoller
    │   ├── time
    │   └── vlan
    ├── devconfig
    │   └── sqvers<span style="color:#f92672">=</span>1.0
    ├── device
    │   └── sqvers<span style="color:#f92672">=</span>2.0
    ├── ifCounters
    │   └── sqvers<span style="color:#f92672">=</span>1.0
    ├── interfaces
    │   └── sqvers<span style="color:#f92672">=</span>3.0
    ├── lldp
    │   └── sqvers<span style="color:#f92672">=</span>1.0
    ├── ospfIf
    │   └── sqvers<span style="color:#f92672">=</span>1.0
    ├── ospfNbr
    │   └── sqvers<span style="color:#f92672">=</span>2.0
    ├── routes
    │   └── sqvers<span style="color:#f92672">=</span>2.0
    ├── sqPoller
    │   └── sqvers<span style="color:#f92672">=</span>2.0
    ├── time
    │   └── sqvers<span style="color:#f92672">=</span>2.0
    └── vlan
        └── sqvers<span style="color:#f92672">=</span>2.0

<span style="color:#ae81ff">42</span> directories
juliopdx@drone:~$
</code></pre></div><p>Here is where things got interesting. In our pipeline, every run stands up a fresh docker container to execute our steps. How can we expose this data to the container to run our Python test scripts against? With a bit of research I came to find this <a href="https://discourse.drone.io/t/how-to-mount-a-device/3536">golden nugget</a>. So it is possible! But there’s another caveat, it must be from trusted repositories. This led me to recreating the drone server and runners, but this time giving my GitHub user account admin privileges. I have updated the previous blog to reflect this change. Below is the same example in the previous blog post.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker run <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --volume<span style="color:#f92672">=</span>/var/lib/drone:/data <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_GITHUB_CLIENT_ID<span style="color:#f92672">=</span>your-id <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_GITHUB_CLIENT_SECRET<span style="color:#f92672">=</span>super-duper-secret <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_RPC_SECRET<span style="color:#f92672">=</span>super-duper-secret <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_SERVER_HOST<span style="color:#f92672">=</span>drone.company.com <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_SERVER_PROTO<span style="color:#f92672">=</span>http <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_USER_CREATE<span style="color:#f92672">=</span>username:<span style="color:#f92672">{{</span>GitHub-Username<span style="color:#f92672">}}</span>,admin:true <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --publish<span style="color:#f92672">=</span>80:80 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --publish<span style="color:#f92672">=</span>443:443 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --restart<span style="color:#f92672">=</span>always <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --detach<span style="color:#f92672">=</span>true <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --name<span style="color:#f92672">=</span>drone <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  drone/drone:2
</code></pre></div><p>After that was done it was just a matter of flipping a flag under the repository settings.</p>
<p><img src="/blog/images/trusted-repo.png" alt="Trusted Repo"></p>
<h3 id="droneyml-updates">.drone.yml Updates</h3>
<p>Now that everything is set correctly, we have to update our pipeline file to include the Suzieq steps and mount the host directory to the runner. Below is an example. Lines 14-17 are naming a volume and then giving it a path that is on our Ubuntu host. Lines 10-12 attach that volume to the runner at a specified path.</p>
<p><code>.drone.yml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Suzieq Check</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">python:3.8</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">pip install suzieq rich</span>
  - <span style="color:#ae81ff">python test_suzieq.py</span>
  <span style="color:#f92672">when</span>:
    <span style="color:#f92672">branch</span>:
    - <span style="color:#ae81ff">master</span>
    - <span style="color:#ae81ff">main</span>
  <span style="color:#f92672">volumes</span>:
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">suzieq</span>
    <span style="color:#f92672">path</span>: <span style="color:#ae81ff">/tmp/suz</span>

<span style="color:#f92672">volumes</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">suzieq</span>
  <span style="color:#f92672">host</span>:
    <span style="color:#f92672">path</span>: <span style="color:#ae81ff">/home/juliopdx/suz</span>
</code></pre></div><h2 id="suzieq-and-python">Suzieq and Python</h2>
<p>The final piece was actually getting a Python script to interact with the mounted data. If you are using Python, Suzieq will look at either <code>./suzieq-cfg.yml</code> or <code>~/.suzieq/suzieq-cfg.yml</code> for a configuration file. There is a lot that can be modified here but the most important I think is where the parquet data is stored. Below is my configuration file for reference.</p>
<p><code>suzieq-cfg.yml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">data-directory</span>: <span style="color:#ae81ff">/tmp/suz/parquet-out</span>
<span style="color:#f92672">temp-directory</span>: <span style="color:#ae81ff">/tmp/suzieq</span>
<span style="color:#f92672">logging-level</span>: <span style="color:#ae81ff">WARNING</span>

<span style="color:#f92672">analyzer</span>:
  <span style="color:#75715e"># By default, the timezone is set to the local timezone. Uncomment</span>
  <span style="color:#75715e"># this line if you want the analyzer (CLI/GUI/REST) to display the time</span>
  <span style="color:#75715e"># in a different timezone than the local time. The timestamp stored in</span>
  <span style="color:#75715e"># the database is always in UTC.</span>
  <span style="color:#f92672">timezone</span>: <span style="color:#ae81ff">America/Los_Angeles</span>
</code></pre></div><h3 id="suzieq-python-examples">Suzieq Python Examples</h3>
<p>Lets start with a few examples in the Python interpreter.</p>
<p><code>Import required libraries</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>suzie-venv<span style="color:#f92672">)</span> juliopdx@drone:~$ python
Python 3.8.10 <span style="color:#f92672">(</span>default, Sep <span style="color:#ae81ff">28</span> 2021, 16:10:42<span style="color:#f92672">)</span>
<span style="color:#f92672">[</span>GCC 9.3.0<span style="color:#f92672">]</span> on linux
Type <span style="color:#e6db74">&#34;help&#34;</span>, <span style="color:#e6db74">&#34;copyright&#34;</span>, <span style="color:#e6db74">&#34;credits&#34;</span> or <span style="color:#e6db74">&#34;license&#34;</span> <span style="color:#66d9ef">for</span> more information.
&gt;&gt;&gt; import pandas as pd
&gt;&gt;&gt; from suzieq.sqobjects import get_sqobject
</code></pre></div><p>Next we will get a sqobject from the data that has been gathered from the Poller. In this case we are grabbing the latest interfaces data.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> int_tbl <span style="color:#f92672">=</span> get_sqobject(<span style="color:#e6db74">&#34;interfaces&#34;</span>)
<span style="color:#f92672">&gt;&gt;&gt;</span> int_tbl()<span style="color:#f92672">.</span>get() <span style="color:#75715e">#data omitted for brevity</span>
   namespace        hostname       ifname state adminState      type    mtu
<span style="color:#ae81ff">0</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>         MGMT    up         up       vrf   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">1</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet5    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">2</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet4    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">3</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet7    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">4</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet6    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">5</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet3    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">6</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet8    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">7</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>  Management1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">8</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">9</span>        eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">02</span>    Ethernet2    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">10</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet6    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">11</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>         MGMT    up         up       vrf   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">12</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet5    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">13</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet4    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">14</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet7    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">15</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">16</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet2    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">17</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Loopback1    up         up  loopback  <span style="color:#ae81ff">65535</span>
<span style="color:#ae81ff">18</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet8    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">19</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>  Management1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">20</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">04</span>    Ethernet3    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">21</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>         MGMT    up         up       vrf   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">22</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet5    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">23</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet7    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">24</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet6    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">25</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet4    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">26</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet3    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">27</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet2    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">28</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet8    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">29</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>  Management1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">30</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">03</span>    Ethernet1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">31</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet5    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">32</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>  Management1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">33</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet8    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">34</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Loopback1    up         up  loopback  <span style="color:#ae81ff">65535</span>
<span style="color:#ae81ff">35</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet2    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">36</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet3    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">37</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet1    up         up  ethernet   <span style="color:#ae81ff">1500</span>
<span style="color:#ae81ff">38</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet6    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">39</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet7    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">40</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>    Ethernet4    up         up  ethernet   <span style="color:#ae81ff">9214</span>
<span style="color:#ae81ff">41</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>         MGMT    up         up       vrf   <span style="color:#ae81ff">1500</span>
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><p>You can also filter on data when making these calls. Maybe we only care about certain interfaces from a particular host?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> int_tbl()<span style="color:#f92672">.</span>get(hostname<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;pdx-rtr-eos-01&#34;</span>], ifname<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;Ethernet1&#34;</span>, <span style="color:#e6db74">&#34;Ethernet2&#34;</span>])
  namespace        hostname     ifname state adminState      type   mtu  ipAddressList
<span style="color:#ae81ff">0</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>  Ethernet2    up         up  ethernet  <span style="color:#ae81ff">1500</span>  [<span style="color:#ae81ff">192.168.1.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>]
<span style="color:#ae81ff">1</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>  Ethernet1    up         up  ethernet  <span style="color:#ae81ff">1500</span>  [<span style="color:#ae81ff">10.0.12.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>]
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><p>Maybe we want to see what device owns a particular route?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> routes_tbl <span style="color:#f92672">=</span> get_sqobject(<span style="color:#e6db74">&#34;routes&#34;</span>)
<span style="color:#f92672">&gt;&gt;&gt;</span> routes_tbl()<span style="color:#f92672">.</span>get(prefix<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;10.0.0.1&#34;</span>], protocol<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;connected&#34;</span>)
  namespace        hostname      vrf       prefix nexthopIps         oifs   protocol
<span style="color:#ae81ff">0</span>       eos  pdx<span style="color:#f92672">-</span>rtr<span style="color:#f92672">-</span>eos<span style="color:#f92672">-</span><span style="color:#ae81ff">01</span>  default  <span style="color:#ae81ff">10.0.0.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">32</span>         []  [Loopback1]  connected
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><p>What I’ve shown you above and what is included in the pipeline post check is just a small subset of what Suzieq can do. Below is a very basic script that will pull data from the parquet directory, depending on a pass or fail, different messages will be printed. If any failures are seen, a counter will run and exit the program with a code of 1. This will signal the pipeline to register the run as a failure. I’ll include a few snapshots of running this in the terminal, the rich library makes even the failures look pretty.</p>
<p><code>test_suzie.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> sys
<span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd
<span style="color:#f92672">from</span> suzieq.sqobjects <span style="color:#f92672">import</span> get_sqobject
<span style="color:#f92672">from</span> rich <span style="color:#f92672">import</span> print


<span style="color:#75715e"># OSPF Testing</span>
ospf_tbl <span style="color:#f92672">=</span> get_sqobject(<span style="color:#e6db74">&#34;ospf&#34;</span>)
ospf_df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>DataFrame(ospf_tbl()<span style="color:#f92672">.</span>aver())
ospf_fail <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
<span style="color:#66d9ef">for</span> index, row <span style="color:#f92672">in</span> ospf_df<span style="color:#f92672">.</span>iterrows():
    <span style="color:#66d9ef">if</span> row[<span style="color:#e6db74">&#34;assert&#34;</span>] <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;pass&#34;</span>:
        print(
            <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;:triangular_flag_on_post: OSPF, </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;hostname&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;ifname&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;assertReason&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74"> :triangular_flag_on_post:&#34;</span>
        )
        ospf_fail <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>

<span style="color:#75715e"># BGP testing</span>
bgp_tbl <span style="color:#f92672">=</span> get_sqobject(<span style="color:#e6db74">&#34;bgp&#34;</span>)
bgp_df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>DataFrame(bgp_tbl()<span style="color:#f92672">.</span>get())
bgp_fail <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
<span style="color:#66d9ef">for</span> index, row <span style="color:#f92672">in</span> bgp_df<span style="color:#f92672">.</span>iterrows():
    <span style="color:#66d9ef">if</span> row[<span style="color:#e6db74">&#34;state&#34;</span>] <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;Established&#34;</span>:
        print(
            <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;:triangular_flag_on_post: </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;hostname&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">(AS </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;asn&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">) to </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;peer&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">(AS </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;peerAsn&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">) is in </span><span style="color:#e6db74">{</span>row[<span style="color:#e6db74">&#39;state&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74"> state :triangular_flag_on_post:&#34;</span>
        )
        bgp_fail <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>

<span style="color:#66d9ef">if</span> bgp_fail <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
    print(<span style="color:#e6db74">&#34;:white_heavy_check_mark: All BGP checks passed :white_heavy_check_mark:&#34;</span>)
<span style="color:#66d9ef">if</span> ospf_fail <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
    print(<span style="color:#e6db74">&#34;:white_heavy_check_mark: All OSPF checks passed :white_heavy_check_mark:&#34;</span>)
<span style="color:#66d9ef">if</span> bgp_fail <span style="color:#f92672">or</span> ospf_fail <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span>:
    sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</code></pre></div><p>Below is an example of me adding a duplicate router ID on pdx-rtr-eos-01.</p>
<p><img src="/blog/images/fail_suzie.png" alt="Fail Suzieq"></p>
<p>Now lets fix that issue and see if we can see the transitions on the network. OSPF neighbors should come back and BGP soon after.</p>
<p><img src="/blog/images/pass-suzie.png" alt="Pass Suzieq"></p>
<p><img src="/blog/images/suzie-bgp.png" alt="Suzie BGP"></p>
<h2 id="suzieq-pipeline-run">Suzieq Pipeline Run</h2>
<p>Below is an example pipeline run after a pull request was submitted and approved. I’ll show you the last step which is new to us in this series. To my knowledge Suzieq is only tested against Python 3.7 and 3.8. Th docker container I created is based on 3.9.7. Drone makes this fairly trivial, as I can just reference another docker image and install the two dependencies we have; Suzieq and Rich.</p>
<p><code>.drone.yml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Suzieq Check</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">python:3.8</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">pip install suzieq rich</span>
  - <span style="color:#ae81ff">python test_suzieq.py</span>
</code></pre></div><p><img src="/blog/images/cicd-pass.png" alt="CI/CD Pass"></p>
<h2 id="bonus-maintain-state-with-cron">BONUS! Maintain State With Cron</h2>
<p>Rapid fire here. Lets just say we had a rogue engineer making all kinds of changes outside of the pipeline. How could we remediate this? Drone and I’m sure other CI tools have a cron feature. Essentially, we can execute pipeline runs at set intervals. Since the main branch is our “production” state. We can set up a cron job to run every day, week, or hour??? I’m going to make some random changes to all four routers and see how the pipeline corrects them. I will set the cron job to run every hour so we don&rsquo;t have to wait a day.. or a week.</p>
<p><img src="/blog/images/cron-job.png" alt="Cron Config"></p>
<p><img src="/blog/images/cron-run.png" alt="Cron Run"></p>
<p><img src="/blog/images/cron-config.png" alt="Cron Config"></p>
<p>As you can see above, all the bogus VLANs and loopbacks have been removed from the configuration.</p>
<h2 id="be-like-rocky">Be Like Rocky</h2>
<blockquote>
<p>You, me, or nobody is gonna hit as hard as life. But it ain’t how hard you hit; it’s about how hard you can get hit, and keep moving forward.</p>
</blockquote>
<p><img src="/blog/images/try-and-try-again.png" alt="Try and Try Again"></p>
<p>I love that Rocky quote. I’m highlighting my failures here to show you that you can do it. When learning something new you may be scratching your head or lost in the sauce. Keep going and when you hit that magical moment, its really really fun.</p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far. This blog series was an absolute treat to write. I hope something along the way has sparked your interest! Please note, what I’ve shown in these posts is just a subset of what these tools can do. Feel free to explore and make it better!</p>
<ul>
<li><a href="https://github.com/JulioPDX/ci_cd_dev">GitHub Repository</a></li>
<li><a href="https://unsplash.com/photos/bL8MDg0p_nI">Featured Image by Frederick Marschall</a></li>
<li><a href="https://suzieq.readthedocs.io/en/latest/">Suzieq Documentation</a></li>
<li><a href="https://elegantnetwork.github.io/posts/closing-the-loop-testing/">Closing the Loop on Testing Network Changes by Ratul Mahajan and Dinesh Dutt</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/">Building a Network CI/CD Pipeline Part 1</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/">Building a Network CI/CD Pipeline Part 2</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/">Building a Network Ci/CD Pipeline Part 3</a></li>
<li><a href="https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/">Building a Network CI/CD Pipeline Part 4</a></li>
<li><a href="https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/">Building a Network CI/CD Pipeline Part 5</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Building a Network CI/CD Pipeline Part 5</title>
            <link>https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/</link>
            <pubDate>Mon, 08 Nov 2021 11:58:46 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/</guid>
            <description>Introduction Hello all, thank you for reading the previous posts in this series. It has been fun interacting with all of you and see a few folks taking the challenge of standing this up on their own. In this post we will go over the framework that is performing our configuration deployments, Nornir and NAPALM. I will also provide an example worklflow on how this is all tied together and executed from a network engineers perspective.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/david-clode.jpg" alt="Featured Image"></p>
<h2 id="introduction">Introduction</h2>
<p>Hello all, thank you for reading the previous posts in this series. It has been fun interacting with all of you and see a few folks taking the challenge of standing this up on their own. In this post we will go over the framework that is performing our configuration deployments, Nornir and NAPALM. I will also provide an example worklflow on how this is all tied together and executed from a network engineers perspective. Please note, the steps that are performed by Nornir can also be performed by something like Ansible.</p>
<h2 id="nornir">Nornir</h2>
<p>My previous automation experience revolved heavily around Ansible for interacting with APIs or network nodes. I really like both tools and I feel each has their space in the network automation world. Where Ansible uses yaml for writing playbooks or steps to be executed on nodes, interacting with Nornir stays within the python ecosystem. This lends itself well to using an IDE like Visual Studio Code and all the other tools available within python (linting, debugging, tab completion).</p>
<h3 id="init-nornir">Init Nornir</h3>
<p>Lets get started with initializing Nornir and setting some of the configuration. A link to all the code used in this post will be at the end. Below we are importing the “InitNornir” function as well as a local function that is used to handle device credentials.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> tools <span style="color:#f92672">import</span> nornir_set_creds
</code></pre></div><p>Below is the main function that is used to initialize Nornir and kick off any tasks that are defined within our deployment script.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Used to run all the things&#34;&#34;&#34;</span>
    norn <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;configs/config.yaml&#34;</span>, core<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;raise_on_error&#34;</span>: <span style="color:#66d9ef">True</span>})
    nornir_set_creds(norn)
    result <span style="color:#f92672">=</span> norn<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>deploy_network)
    print_result(result)


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    main()
</code></pre></div><p>At this point we are initializing Nornir with a file located at “configs/config.yaml”, lets take a look at that directory.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ tree configs/
configs/
├── config.yaml
├── groups.yaml
└── hosts.yaml
</code></pre></div><p>This makes me chuckle a bit, I just mentioned that Nornir is pretty much all python and Ansible uses yaml for developing playbooks. Yet here we are making yaml files. Jokes aside, yaml is really good at being human readable and building configuration files. Lets take a look at the “config.yaml” file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">inventory</span>:
  <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">SimpleInventory</span>
  <span style="color:#f92672">options</span>:
    <span style="color:#f92672">host_file</span>: <span style="color:#e6db74">&#34;./configs/hosts.yaml&#34;</span>
    <span style="color:#f92672">group_file</span>: <span style="color:#e6db74">&#34;./configs/groups.yaml&#34;</span>
<span style="color:#f92672">runner</span>:
  <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">threaded</span>
  <span style="color:#f92672">options</span>:
    <span style="color:#f92672">num_workers</span>: <span style="color:#ae81ff">10</span>
</code></pre></div><p>This file can be used to set all kinds of settings. I’ve tried to keep it fairly simple, we are using the “SimpleInventory” plugin. This can read in our hosts and groups file that I will go over in a bit. Other inventroy plugins (Ansible, Netbox, table_inventory) can also be used, check more out <a href="https://nornir.tech/nornir/plugins/">here</a>. The second half of the file is utilizing the built-in threaded runner. Nornir will spin up threads to allow task executions to multiple devices vs executed one by one. Next up lets check out the “groups.yaml” file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">eos</span>:
  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">eos</span>
</code></pre></div><p>As you can see I have barely used this file. If we were running multiple vendors or maybe devices located at different sites, they could be assigned different configuration options that would be added within this groups file. Below is an example of that concept in case my explanation is a bit terrible.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">eos</span>:
  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">eos</span>
<span style="color:#f92672">cisco_ios</span>:
  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">ios</span>
<span style="color:#f92672">branch</span>:
  <span style="color:#f92672">dns</span>:
  - <span style="color:#ae81ff">8.8.8.8</span>
  - <span style="color:#ae81ff">1.1.1.1</span>
<span style="color:#f92672">dc</span>:
  <span style="color:#f92672">dns</span>:
  - <span style="color:#ae81ff">10.0.0.125</span>
  - <span style="color:#ae81ff">10.0.0.126</span>
</code></pre></div><p>Last but not least the “hosts.yaml” file. For small deployments I can see how maintaining this file wouldn’t be the worst thing in the world. For scale, I would use something like Netbox to build your inventory file. All nodes in this case are assigned to the “eos” group and will inherit “eos” as the platform.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">pdx-rtr-eos-01</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">192.168.10.121</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">eos</span>

<span style="color:#f92672">pdx-rtr-eos-02</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">192.168.10.122</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">eos</span>

<span style="color:#f92672">pdx-rtr-eos-03</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">192.168.10.123</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">eos</span>

<span style="color:#f92672">pdx-rtr-eos-04</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">192.168.10.124</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">eos</span>
</code></pre></div><h3 id="deploy-configurations">Deploy Configurations</h3>
<p>Earlier I showed you the main function which is calling on a certain task to be executed. Below is the main function again.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Used to run all the things&#34;&#34;&#34;</span>
    norn <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;configs/config.yaml&#34;</span>, core<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;raise_on_error&#34;</span>: <span style="color:#66d9ef">True</span>})
    nornir_set_creds(norn)
    result <span style="color:#f92672">=</span> norn<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>deploy_network)
    print_result(result)
</code></pre></div><p>The main function will call the “deploy_network” function, which will be executed against our network devices in the inventory. Below is the “deploy_network” function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_configure

<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">deploy_network</span>(task):
    <span style="color:#e6db74">&#34;&#34;&#34;Configures network with NAPALM&#34;&#34;&#34;</span>
    task1_result <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Configuring </span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">!&#34;</span>,
        task<span style="color:#f92672">=</span>napalm_configure,
        filename<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;./snapshots/configs/</span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">.txt&#34;</span>,
        dry_run<span style="color:#f92672">=</span>args<span style="color:#f92672">.</span>dry,
        replace<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
    )
</code></pre></div><p>We are adding one more import to our script. The “napalm_configure” function is part of the nornir_napalm library. Under the hood it will use NAPALM to connect and configure network devices. The plugin options are pretty extensive depending on what your use case is (deploying entire config or just a subset). This function will look at the file name that matches our device hostname. It will then attempt to push the entire configuration to the device.</p>
<h3 id="side-note-on-infrastructure-as-code">Side Note on Infrastructure as Code</h3>
<p>For this example we have the device configurations located under the snapshots directory. The entire configuration can be viewed and modified. For a IaC approach, you might develop some yaml or json data that can then be passed into a jinja template to define a configuration file. This file can then be used to configure your entire device. The data files could contain interface settings, routing information, services, pretty much anything that is part of the configuration. Check out this great blog series by NWMichl on <a href="https://nwmichl.net/2020/10/28/network-infrastructure-as-code-with-ansible-part-1/">Network Infrastructure as Code</a>.</p>
<h2 id="workflow">Workflow</h2>
<p>Lets take a look at our reference architecture below to get an idea of the workflow we will run through to make a change to our network.</p>
<p><img src="/blog/images/ci_cd_blog.png" alt="High Level Design"></p>
<p>At the bottom of our diagram we have a simple OSPF and BGP network with four Arista devices. We have a task for our junior network engineer to add interface descriptions to all interfaces connected to networking nodes. Since this is a really safe change with no impact to the network, besides maybe confusing some operators if they are incorrect. This seems like a great learning opportunity for the junior engineer.</p>
<p>The engineer starts by cloning the teams repository from GitHub.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">git clone https://github.com/JulioPDX/ci_cd_dev.git
</code></pre></div><p>Great, now the repository is local to our development environment. We would most likely have some standards to not allow direct commits to the main branch. All changes must be made to a separate branch and then merged by pull request to main. Main in this case will signal our production configuration state. Next up the junior engineer will create a dev branch named “add_interface_desc”.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ git checkout -b add_interface_desc
Switched to a new branch <span style="color:#e6db74">&#39;add_interface_desc&#39;</span>
<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ git status
On branch add_interface_desc
nothing to commit, working tree clean
</code></pre></div><p>Now the engineer is on the new branch and can modify the configuration files in the snapshots directory. Lets look at what files have been modified.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ git status
On branch add_interface_desc
Changes not staged <span style="color:#66d9ef">for</span> commit:
  <span style="color:#f92672">(</span>use <span style="color:#e6db74">&#34;git add &lt;file&gt;...&#34;</span> to update what will be committed<span style="color:#f92672">)</span>
  <span style="color:#f92672">(</span>use <span style="color:#e6db74">&#34;git restore &lt;file&gt;...&#34;</span> to discard changes in working directory<span style="color:#f92672">)</span>
        modified:   snapshots/configs/pdx-rtr-eos-01.txt
        modified:   snapshots/configs/pdx-rtr-eos-02.txt
        modified:   snapshots/configs/pdx-rtr-eos-03.txt
        modified:   snapshots/configs/pdx-rtr-eos-04.txt

no changes added to commit <span style="color:#f92672">(</span>use <span style="color:#e6db74">&#34;git add&#34;</span> and/or <span style="color:#e6db74">&#34;git commit -a&#34;</span><span style="color:#f92672">)</span>
</code></pre></div><p>Great, now lets push those changes up to the remote repository.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ git add .
<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ git commit -m <span style="color:#e6db74">&#34;Adding interface descriptions&#34;</span>
<span style="color:#f92672">[</span>add_interface_desc 9b8d899<span style="color:#f92672">]</span> Adding interface descriptions
 <span style="color:#ae81ff">4</span> files changed, <span style="color:#ae81ff">6</span> insertions<span style="color:#f92672">(</span>+<span style="color:#f92672">)</span>
<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ git push -u origin add_interface_desc
Enumerating objects: 15, <span style="color:#66d9ef">done</span>.
Counting objects: 100% <span style="color:#f92672">(</span>15/15<span style="color:#f92672">)</span>, <span style="color:#66d9ef">done</span>.
Delta compression using up to <span style="color:#ae81ff">8</span> threads
Compressing objects: 100% <span style="color:#f92672">(</span>7/7<span style="color:#f92672">)</span>, <span style="color:#66d9ef">done</span>.
Writing objects: 100% <span style="color:#f92672">(</span>8/8<span style="color:#f92672">)</span>, <span style="color:#ae81ff">781</span> bytes | 390.00 KiB/s, <span style="color:#66d9ef">done</span>.
Total <span style="color:#ae81ff">8</span> <span style="color:#f92672">(</span>delta 5<span style="color:#f92672">)</span>, reused <span style="color:#ae81ff">0</span> <span style="color:#f92672">(</span>delta 0<span style="color:#f92672">)</span>, pack-reused <span style="color:#ae81ff">0</span>
remote: Resolving deltas: 100% <span style="color:#f92672">(</span>5/5<span style="color:#f92672">)</span>, completed with <span style="color:#ae81ff">5</span> local objects.
remote:
remote: Create a pull request <span style="color:#66d9ef">for</span> <span style="color:#e6db74">&#39;add_interface_desc&#39;</span> on GitHub by visiting:
remote:      https://github.com/JulioPDX/ci_cd_dev/pull/new/add_interface_desc
remote:
To https://github.com/JulioPDX/ci_cd_dev.git
 * <span style="color:#f92672">[</span>new branch<span style="color:#f92672">]</span>      add_interface_desc -&gt; add_interface_desc
Branch <span style="color:#e6db74">&#39;add_interface_desc&#39;</span> set up to track remote branch <span style="color:#e6db74">&#39;add_interface_desc&#39;</span> from <span style="color:#e6db74">&#39;origin&#39;</span>.
<span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$
</code></pre></div><p>From our previous work this should have kicked off a pipeline run without making any actual changes to the nodes.</p>
<p><img src="/blog/images/branch_run.png" alt="Branch Run"></p>
<p><img src="/blog/images/pre_change.png" alt="Pre Change"></p>
<p>At this point every precheck stage has passed in the pipeline and we can see what the changes would be. We can now create a pull request and merge these changes into our main branch.</p>
<p><img src="/blog/images/compare_pull.png" alt="Compare Pull"></p>
<p><img src="/blog/images/open_pr.png" alt="Open PR"></p>
<p><img src="/blog/images/check_passed.png" alt="Check Passed"></p>
<p>Above we can see the branch has a passing build. The reviewer or senior engineer can now merge the pull request. After that is done another pipeline run should execute but this time the changes will be applied to our network.</p>
<p><img src="/blog/images/prod_run.png" alt="Prod Run"></p>
<p>Now there is a new stage in the pipeline…deploy configurations! Lets take a look at one of the devices and see if the change was indeed applied.</p>
<p><img src="/blog/images/SCRT_view.png" alt="Config View"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>The change we made was really trivial but its a good way to show the new process in the world of CI/CD. Thanks again for checking out this post. I think we have one more to go in the series. The last one will go over using Suzieq for network observability and post change testing.</p>
<ul>
<li><a href="https://github.com/JulioPDX/ci_cd_dev">GitHub Repository</a></li>
<li><a href="https://unsplash.com/photos/o8C6UFpqC4s">Featured image by David Clode</a></li>
<li><a href="https://nornir.readthedocs.io/en/latest/">Nornir</a></li>
<li><a href="https://nwmichl.net/2020/10/28/network-infrastructure-as-code-with-ansible-part-1/">Infrastructure as Code Blog Series by NWMichl</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/">Building a Network CI/CD Pipeline Part 1</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/">Building a Network CI/CD Pipeline Part 2</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/">Building a Network Ci/CD Pipeline Part 3</a></li>
<li><a href="https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/">Building a Network CI/CD Pipeline Part 4</a></li>
<li><a href="https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/">Building a Network CI/CD Pipeline Part 6</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Building a Network CI/CD Pipeline Part 4</title>
            <link>https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/</link>
            <pubDate>Sun, 31 Oct 2021 11:26:48 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/</guid>
            <description>Introduction Hello everyone and thank you for checking out part four in this series. I went on vacation for a bit, but I’m glad to be back on the keys. In this post I will break down all of the steps performed before a change hits our network devices. This is important because we have the opportunity to stop incorrect or invalid configurations from ever hitting our network devices. A few steps that we will cover are: Black for code formatting, Batfish to validate configuration updates, and NAPALM dry run for testing the legitimacy of the configuration files.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/masaaki-komori.jpg" alt="Fish"></p>
<h2 id="introduction">Introduction</h2>
<p>Hello everyone and thank you for checking out part four in this series. I went on vacation for a bit, but I’m glad to be back on the keys. In this post I will break down all of the steps performed before a change hits our network devices. This is important because we have the opportunity to stop incorrect or invalid configurations from ever hitting our network devices. A few steps that we will cover are: Black for code formatting, Batfish to validate configuration updates, and NAPALM dry run for testing the legitimacy of the configuration files.</p>
<p><img src="/blog/images/ci_cd_blog.png" alt="High Level Design"></p>
<h2 id="black-code-style">Black Code Style</h2>
<p>When adding steps to this pipeline I have concentrated on easy wins and easy tools to add to the chain. One of the slickest of them being Black. Black is a code formatter based on the Python <a href="https://www.python.org/dev/peps/pep-0008/">PEP 8</a> standard. You can see the steps in the pipeline file below.</p>
<p><code>.drone.yml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">steps</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Black Code Format Check</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">juliopdx/netauto</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">black . --check</span>
</code></pre></div><p>Black will traverse all Python files in our directory and fail if any changes are required. The “–check” option will trigger the failure in our pipeline. Below is an example of what a failure would look like. I added a new file to our repository with a long list variable.</p>
<p><code>my_list.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">my_list <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;something1&#34;</span>, <span style="color:#e6db74">&#34;something2&#34;</span>, <span style="color:#e6db74">&#34;something3&#34;</span>, <span style="color:#e6db74">&#34;something4&#34;</span>, <span style="color:#e6db74">&#34;something5&#34;</span>, <span style="color:#e6db74">&#34;something6&#34;</span>]
</code></pre></div><p>I will commit and push this up to our dev branch.</p>
<p><img src="/blog/images/pipe_fail.png" alt="Pipe Fail"></p>
<p>The Black format check has failed and listed what files would need to be updated. This is very useful to maintain some set standards for yourself or the team on the code style you choose to follow. Below is an example of a success, in this case I have updated that new Python file. Another option to add in this step is something like Pylint. At this time I have not added this to the pipeline but I encourage the reader to check it out. Pylint will go as far as giving the code a score and provide improvement options.</p>
<p><code>my_list.py updated</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">my_list <span style="color:#f92672">=</span> [
    <span style="color:#e6db74">&#34;something1&#34;</span>,
    <span style="color:#e6db74">&#34;something2&#34;</span>,
    <span style="color:#e6db74">&#34;something3&#34;</span>,
    <span style="color:#e6db74">&#34;something4&#34;</span>,
    <span style="color:#e6db74">&#34;something5&#34;</span>,
    <span style="color:#e6db74">&#34;something6&#34;</span>,
]
</code></pre></div><p><img src="/blog/images/black_fixed.png" alt="Black Fixed"></p>
<h2 id="validating-changes-with-batfish">Validating Changes With Batfish</h2>
<p>Batfish dubs itself as “an open source network configuration analysis tool”. Batfish can validate pre-deployment checks by modeling the network from a source of configurations. This could vary from mismatched BGP neighbor settings, OSPF mismatches, and many more. I’ll walk through installing the Batfish service and run through some examples of what it can find!</p>
<p>Lets get Batfish up and running. Thankfully this can be done by running the latest docker container. This is building on what we have already created from previous articles!</p>
<p><code>Batfish Docker Run</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker run --name batfish <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    -v batfish-data:/data  <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    -p 9997:9997 -p 9996:9996 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    -d batfish/batfish
</code></pre></div><p>Great, now that the service is running we need to create a configuration snapshot. Think of this as our production network or the changes we would like to push to our production network. This could be one site or the whole enterprise. Be mindful, you may need more horsepower if you are testing against a large network. In our case we have a very simple four node topology running OSPF and BGP between R1 and R4. To get the configuration snapshot up and running I created a simple backup script to store the files in the correct location.</p>
<p><code>Directory Structure</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-pop:~/git/ci_cd_dev$ tree snapshots/
snapshots/
└── configs
    ├── pdx-rtr-eos-01.txt
    ├── pdx-rtr-eos-02.txt
    ├── pdx-rtr-eos-03.txt
    └── pdx-rtr-eos-04.txt
</code></pre></div><h2 id="batfish-assertion-helpers">Batfish Assertion Helpers</h2>
<p>While exploring Batfish, I ran into their <a href="https://batfish.readthedocs.io/en/latest/asserts.html">assertion helpers</a> page. There is some gold in that page and I highly recommend you check it out. I used these heavily in this test deployment to see what errors Batfish would find in the configuration. Pretty much all of these require the same bit of information (a snapshot is required). I’ll walk through the script and one of the assert functions, the rest follow the same path.</p>
<p>Below are some of the standard pybatfish imports followed by a decent amount of the assert helpers. Importing Rich because it’s awesome and I like colors.</p>
<p><code>Imports</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> pybatfish.client.commands <span style="color:#f92672">import</span> <span style="color:#f92672">*</span>
<span style="color:#f92672">from</span> pybatfish.question <span style="color:#f92672">import</span> load_questions
<span style="color:#f92672">from</span> pybatfish.client.asserts <span style="color:#f92672">import</span> (
    assert_no_duplicate_router_ids,
    assert_no_incompatible_bgp_sessions,
    assert_no_incompatible_ospf_sessions,
    assert_no_unestablished_bgp_sessions,
    assert_no_undefined_references,
)
<span style="color:#f92672">from</span> rich <span style="color:#f92672">import</span> print <span style="color:#66d9ef">as</span> rprint
</code></pre></div><p>Below is the function used to test if we have duplicate router IDs. It requires one argument of snap or snapshot.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">test_duplicate_rtr_ids</span>(snap):
    <span style="color:#e6db74">&#34;&#34;&#34;Testing for duplicate router IDs&#34;&#34;&#34;</span>
    assert_no_duplicate_router_ids(
        snapshot<span style="color:#f92672">=</span>snap,
        protocols<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;ospf&#34;</span>, <span style="color:#e6db74">&#34;bgp&#34;</span>},
    )
</code></pre></div><p>This function will look at BGP and OSPF within our snapshot to see if duplicate router IDs are set. I omitted a few Rich prints because they are not pertinent to the functionality of the function. Below if the main function that calls all of our assert helpers. A lot of this is created from samples in the Batfish documentation. For example, we name our snapshot and point it to the correct directory. We also point the script to the correct IP that is running the service.</p>
<p><code>Main Function</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;init all the things&#34;&#34;&#34;</span>
    NETWORK_NAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;PDX_NET&#34;</span>
    SNAPSHOT_NAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;snapshot00&#34;</span>
    SNAPSHOT_DIR <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;./snapshots&#34;</span>
    bf_session<span style="color:#f92672">.</span>host <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;192.168.10.184&#34;</span>
    bf_set_network(NETWORK_NAME)
    init_snap <span style="color:#f92672">=</span> bf_init_snapshot(SNAPSHOT_DIR, name<span style="color:#f92672">=</span>SNAPSHOT_NAME, overwrite<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
    load_questions()
    test_duplicate_rtr_ids(init_snap)
    test_bgp_compatibility(init_snap)
    test_ospf_compatibility(init_snap)
    test_bgp_unestablished(init_snap)
    test_undefined_references(init_snap)


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    main()
</code></pre></div><h2 id="test-for-duplicate-router-ids">Test for Duplicate Router IDs</h2>
<p>All of our routers have RIDs set as 10.0.0.x, x being the router number. Lets change the router ID under OSPF for R1 to be 10.0.0.2, this is done under the snapshot directory configuration file for R1. For an infrastructure as code build these would be created from YAML files or some source of truth and maybe some jinja sprinkled in there. This change would be a duplicate of R2 and we should see the pipeline fail.</p>
<p><code>R1 OSPF Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router ospf 1
   router-id 10.0.0.2
   passive-interface Ethernet2
   passive-interface Loopback1
   max-lsa 12000
</code></pre></div><p>We will commit and push this to our dev branch, the pipeline output is below. In the bottom right you can barely see the duplicate router IDs error and towards the bottom of the output it will list the devices that are in error. In this case both R1 and R2 have router IDs set to “10.0.0.2”.</p>
<p><img src="/blog/images/rid_error.png" alt="RID Error"></p>
<h2 id="test-for-incompatible-bgp-sessions">Test for Incompatible BGP Sessions</h2>
<p>Lets add another error. In this case R1 and R4 are running multihop EBGP. R1 has an autonomous system (AS) of 65001 and R4 has an AS of 65004. Lets just say someone misconfigured BGP and set R1 to have a neighbor of 65003 vs 65004. As we can see R1 is set to have a neighbor with R4 using AS 65003 but R4 is configured with AS 65004. You may have noticed towards the top of the pipeline that no duplicate router IDs were found in this run.</p>
<p><code>R1 Bad BGP</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 65001
   router-id 10.0.0.1
   neighbor 10.0.0.4 remote-as 65003
   neighbor 10.0.0.4 update-source Loopback1
   neighbor 10.0.0.4 ebgp-multihop 3
</code></pre></div><p><img src="/blog/images/bgp_error.png" alt="BGP Error"></p>
<h2 id="test-for-ospf-network-mismatches">Test for OSPF Network Mismatches</h2>
<p>Okay this is the last example we will use but I think you all get the idea. All OSPF neighbors have interfaces configured with point to point interfaces. Lets remove the R1 to R2 point to point interface configuration in our snapshot. This should result in a network type mismatch error.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">interface Ethernet1
   no switchport
   ip address 10.0.12.1/24
 - ip ospf network point-to-point
   ip ospf area 0.0.0.0
</code></pre></div><p><img src="/blog/images/ospf_error.png" alt="OSPF Error"></p>
<p>At this point we have prevented a few configuration errors from entering the network. Please note, this is only scratching the surface on what can be done with Batfish. Please check out their documentation and code examples on more ideas to test your network. Links at the end of this post.</p>
<h2 id="validating-configuration-with-napalm-dry-run">Validating Configuration With NAPALM Dry Run</h2>
<p>Batfish is great and it can catch a lot of errors or misconfigurations. What if there is something that can’t be caught by Batfish as a configuration error. How would we validate what we are sending is even a valid configuration in general? In this case I leveraged Nornir with NAPALM. NAPALM has a “napalm_configure” task that will attempt to send a complete configuration file to a network device. If it is not valid, it should report an error. Remember that we are in the precheck stage and don’t want any actual changes to hit our network devices. Below is a snippet on what I came up with to keep the code small but also functional between precheck deployments and actual deployments.</p>
<p><code>build.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> argparse
<span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_configure
<span style="color:#f92672">from</span> nornir_utils.plugins.functions <span style="color:#f92672">import</span> print_result
<span style="color:#f92672">from</span> tools <span style="color:#f92672">import</span> nornir_set_creds


parser <span style="color:#f92672">=</span> argparse<span style="color:#f92672">.</span>ArgumentParser()
parser<span style="color:#f92672">.</span>add_argument(
    <span style="color:#e6db74">&#34;--dry_run&#34;</span>, dest<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;dry&#34;</span>, action<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;store_true&#34;</span>, help<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Will not run on devices&#34;</span>
)
parser<span style="color:#f92672">.</span>add_argument(
    <span style="color:#e6db74">&#34;--no_dry_run&#34;</span>, dest<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;dry&#34;</span>, action<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;store_false&#34;</span>, help<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Will run on devices&#34;</span>
)
parser<span style="color:#f92672">.</span>set_defaults(dry<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
args <span style="color:#f92672">=</span> parser<span style="color:#f92672">.</span>parse_args()


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">deploy_network</span>(task):
    <span style="color:#e6db74">&#34;&#34;&#34;Configures network with NAPALM&#34;&#34;&#34;</span>
    task1_result <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Configuring </span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">!&#34;</span>,
        task<span style="color:#f92672">=</span>napalm_configure,
        filename<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;./snapshots/configs/</span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">.txt&#34;</span>,
        dry_run<span style="color:#f92672">=</span>args<span style="color:#f92672">.</span>dry,
        replace<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
    )


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Used to run all the things&#34;&#34;&#34;</span>
    norn <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;configs/config.yaml&#34;</span>, core<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;raise_on_error&#34;</span>: <span style="color:#66d9ef">True</span>})
    nornir_set_creds(norn)
    result <span style="color:#f92672">=</span> norn<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>deploy_network)
    print_result(result)


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    main()
</code></pre></div><p>The top portions of the script import anything required to interact with Nornir and NAPALM. We then use “argparse” to create an argument with the script that will set a variable to True or False. This can then be used during the “napalm_configure” task to either run in dry mode or actually implement changes. Below is how the precheck looks like in the “.drone.yml” file.</p>
<p><code>Configuration Precheck</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Precheck Configuration Diff</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">juliopdx/netauto</span>
  <span style="color:#f92672">environment</span>:
    <span style="color:#f92672">MY_SECRET</span>:
      <span style="color:#f92672">from_secret</span>: <span style="color:#ae81ff">MY_SECRET</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">python build.py --dry_run</span>
</code></pre></div><h2 id="napalm-dry-run-with-error">NAPALM Dry Run With Error</h2>
<p>Below is an example of adding something to the configuration that will definitely not work and how it looks like in the pipeline.</p>
<p><code>Error Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 65001
   router-id 10.0.0.1
   neighbor 10.0.0.4 remote-as 65004
   neighbor 10.0.0.4 update-source Loopback1
   neighbor 10.0.0.4 ebgp-multihop 3
!
something super fake
   welcome to the world of tomorrow
   its over 9000
   router-id infinity
!
router ospf 1
   router-id 10.0.0.1
   passive-interface Ethernet2
   passive-interface Loopback1
   max-lsa 12000
!
</code></pre></div><p><img src="/blog/images/invalid_config.png" alt="Invalid Config"></p>
<h2 id="napalm-dry-run-with-valid-configuration">NAPALM Dry Run With Valid Configuration</h2>
<p>Below is a standard change, adding a simple description to an interface. Included pipeline output as well.</p>
<p><code>Valid Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!
interface Ethernet1
   description Welcome to the world of tomorrow!
   no switchport
   ip address 10.0.12.1/24
   ip ospf network point-to-point
   ip ospf area 0.0.0.0
!
</code></pre></div><p><img src="/blog/images/valid_config.png" alt="Valid Configuration"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Whats included in this post is just a subset of options that are available in prechecks. We could add ACL, input validation, Pylint, routing tests, and more. I hope what’s included here sparks some ideas or gives you something to add to your CI/CD workflow. I think in the next post we will go over the tool used to actually deploy configurations once they have passed our prechecks. In our case Nornir, but this could be Ansible, Scrapli, or some other solution.</p>
<ul>
<li><a href="https://unsplash.com/photos/vXid97obEy8">Featured Image by Masaaki Komori</a></li>
<li><a href="https://black.readthedocs.io/en/stable/">Black Code Formatting</a></li>
<li><a href="https://pybatfish.readthedocs.io/en/latest/">Batfish and pybatfish</a></li>
<li><a href="https://github.com/nornir-automation/nornir_napalm">Nornir NAPALM</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/">Building a Network CI/CD Pipeline Part 1</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/">Building a Network CI/CD Pipeline Part 2</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/">Building a Network Ci/CD Pipeline Part 3</a></li>
<li><a href="https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/">Building a Network CI/CD Pipeline Part 5</a></li>
<li><a href="https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/">Building a Network CI/CD Pipeline Part 6</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Building a Network CI/CD Pipeline Part 3</title>
            <link>https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/</link>
            <pubDate>Wed, 20 Oct 2021 11:05:49 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/</guid>
            <description>Introduction Thank you for checking out part three in this series, it really means a lot! So far we have installed Docker on an Ubuntu host machine and the Drone server/runners. In this post we will go over the .drone.yml file and why I decided to create my first Docker container image. Stick around after the break…. who am I kidding there is no break, what is this an infomercial?</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/ian-taylor.jpg" alt="Containers"></p>
<h2 id="introduction">Introduction</h2>
<p>Thank you for checking out part three in this series, it really means a lot! So far we have installed Docker on an Ubuntu host machine and the Drone server/runners. In this post we will go over the <em>.drone.yml</em> file and why I decided to create my first Docker container image. Stick around after the break…. who am I kidding there is no break, what is this an infomercial?</p>
<h2 id="droneyml-or-droneyaml-or--something">.drone.yml or .drone.yaml or .. something?</h2>
<p>Pretty much all CI systems use some kind of configuration file, essentially the steps you want performed on your pipeline run. They all have their own flavor, but essentially walk the same line. My only previous experience was with Azure Devops pipelines. The <em>.drone.yml</em> file should be placed at the root of your directory structure of the project. Below is one of the first versions of the configuration file in my repository.</p>
<p><code>Initial .drone.yml file</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">pipeline</span>
<span style="color:#f92672">type</span>: <span style="color:#ae81ff">docker</span>
<span style="color:#f92672">name</span>: <span style="color:#ae81ff">Testing Python CI/CD</span>


<span style="color:#f92672">steps</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Install and deploy</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">python</span>
  <span style="color:#f92672">environment</span>:
    <span style="color:#f92672">MY_PASS</span>:
      <span style="color:#f92672">from_secret</span>: <span style="color:#ae81ff">MY_PASS</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">pip3 install -r requirements.txt</span>
  - <span style="color:#ae81ff">python3 bat.py</span>

<span style="color:#f92672">trigger</span>:
  <span style="color:#f92672">branch</span>:
    <span style="color:#f92672">exclude</span>:
    - <span style="color:#ae81ff">main</span>
    - <span style="color:#ae81ff">master</span>
</code></pre></div><p>Lines 1-3 go over the highest level settings of the pipeline. We are using a docker type, since we installed the docker runners. If you are using something like exec, you would have to place that there and install exec runners as well. Lines 6-14 describe the step or steps performed. My simple example is running one step and executing two commands. The image being used is a python Docker image from Docker hub. Once that is pulled down, the commands will execute on the container. The first will install some requirements from my code base and the second will execute the script bat.py. The script itself is just performing a simple connection to devices in my lab. We are utilizing the secret we set earlier in the repository settings here in the script.</p>
<h2 id="the-problem-with-requirements">The Problem With Requirements</h2>
<p>If your environment has very little or no external dependencies, you may not run into this problem. As I got the pipeline working and starting adding more to the code base, this also added more requirements that needed to be installed during the pipeline run. The server created that is hosting these docker containers is not very powerful. That could be the cause of some of these delays. Check out a pipeline run below(a bit exaggerated but you get the point).</p>
<p><img src="/blog/images/long-install.png" alt="Long install"></p>
<p>17 minutes!!! Yikes! I think software development has a concept of feedback loops. Where we would like to know if something passed or failed relatively quickly. Over time this would be crazy, imagine waiting that long to find out you had a small code syntax error, I guess it would make for a good time to get a coffee. This then led me down the path of creating a Docker container image.</p>
<h2 id="creating-a-docker-container-image-optional">Creating a Docker Container Image (Optional)</h2>
<p>So whats the big deal with this container image? For one, I’ve never even come close to thinking of making one. I’m trying to show you, the reader ,that if there is some technology you don’t know or aren’t comfortable with, with a little time and persistence you can make really cool things happen. I wanted to see if there was some way to make a lightweight python container and then just add on my requirements. I did what any sane person would do… I hit up my friend Google.</p>
<p>Google then led me to this incredible write up by Samuel James, check it out <a href="https://stackify.com/docker-build-a-beginners-guide-to-building-docker-images/">here</a>. We already had Docker installed on our Ubuntu server so that wasn’t required. I did have to create a Docker Hub account and run “docker login”. Once logged in I proceeded with creating the Dockerfile.</p>
<h2 id="dockerfile">Dockerfile</h2>
<p>The Dockerfile essentially describes how you want to build your image. Check out my example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-dockerfile" data-lang="dockerfile"><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> python:3.9.7-slim</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># Install dependencies:</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> requirements.txt .<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> pip install -r requirements.txt<span style="color:#960050;background-color:#1e0010">
</span></code></pre></div><p>That’s about it… no, I’m kidding. The FROM statement is telling docker if we want to base our image from any other image. For this example we are using the python 3.9.7-slim variant. After that I have a requirements.txt file locally that will be copied to the container. Then RUN will execute that command on the container, which will install our dependencies.</p>
<p><code>requirements.txt</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">netmiko
rich
black
pybatfish
napalm
pytest
nornir
nornir-utils
nornir-napalm
</code></pre></div><h2 id="build-container-and-publish">Build Container and Publish</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker build -t juliopdx/netauto .
docker push juliopdx/netauto:latest
</code></pre></div><p>Now that we have a new container image we can use this in our <em>.drone.yml</em> file. If you are really curious check out the docker hub page <a href="https://hub.docker.com/r/juliopdx/netauto">here</a>. Isn’t it pretty? Check out the updated configuration file below.</p>
<p><code>.drone.yml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">pipeline</span>
<span style="color:#f92672">type</span>: <span style="color:#ae81ff">docker</span>
<span style="color:#f92672">name</span>: <span style="color:#ae81ff">Testing Python CI/CD</span>


<span style="color:#f92672">steps</span>:
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Black Code Format Check</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">juliopdx/netauto</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">black . --check</span>

- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Precheck OSPF and BGP</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">juliopdx/netauto</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">pytest test.py --disable-pytest-warnings -s</span>

- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deploy Configurations</span>
  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">juliopdx/netauto</span>
  <span style="color:#f92672">environment</span>:
    <span style="color:#f92672">MY_PASS</span>:
      <span style="color:#f92672">from_secret</span>: <span style="color:#ae81ff">MY_PASS</span>
  <span style="color:#f92672">commands</span>:
  - <span style="color:#ae81ff">python build.py</span>
  <span style="color:#f92672">when</span>:
    <span style="color:#f92672">branch</span>:
    - <span style="color:#ae81ff">master</span>
    - <span style="color:#ae81ff">main</span>

<span style="color:#f92672">trigger</span>:
  <span style="color:#f92672">event</span>:
    <span style="color:#f92672">exclude</span>:
    - <span style="color:#ae81ff">pull_request</span>
</code></pre></div><p>Lines 1-3 are essentially the same. I updated the steps as well. The first step just makes sure we are adhering to black formatting standards. If there is a file to change the pipeline will fail. The second step runs a few Batfish checks on OSPF and BGP, more on that in a later post. The last step will actually deploy configurations to our test network, but only if it is a main or master branch. The trigger at the bottom is just used to exclude pull requests. Every step pulls down the exact same image (I believe only pulling it down once), to my knowledge each step is actually executed individually. So requirements installed in one step would not exist in another. Okay okay, but what about the pipeline deployment time? Please see below.</p>
<p><img src="/blog/images/deploy.png" alt="Deploy"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Isn’t that the most beautiful thing ever? A whole 19 seconds &lt;3. Seriously thank you all for reading this far. Next up I think we will go over adding Batfish to the equation. Stayed tuned for the next episode…</p>
<ul>
<li><a href="https://docs.drone.io/pipeline/docker/syntax/">.drone.yml Configuration Options</a></li>
<li><a href="https://stackify.com/docker-build-a-beginners-guide-to-building-docker-images/">Docker Build: A Beginner’s Guide to Building Docker Images by Samuel James</a></li>
<li><a href="https://unsplash.com/photos/jOqJbvo1P9g">Featured image by Ian Taylor</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/">Building a Network CI/CD Pipeline Part 1</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/">Building a Network CI/CD Pipeline Part 2</a></li>
<li><a href="https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/">Building a Network Ci/CD Pipeline Part 4</a></li>
<li><a href="https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/">Building a Network CI/CD Pipeline Part 5</a></li>
<li><a href="https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/">Building a Network CI/CD Pipeline Part 6</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Building a Network CI/CD Pipeline Part 2</title>
            <link>https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/</link>
            <pubDate>Wed, 20 Oct 2021 10:41:26 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/</guid>
            <description>Introduction Thank you for checking out part two of this series. The last post went through installing docker on an Ubuntu server to begin our journey to building a CI/CD pipeline. In this post it will be all about connecting Drone to our code repository, in this case GitHub. From our reference diagram below, we will be focusing on the connections from GitHub to the Drone runners.
The Pipeline Server and Runners Now that we have docker installed, we need something to test our code or execute it.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/jonathan-lampel.jpg" alt="Featured Image"></p>
<h2 id="introduction">Introduction</h2>
<p>Thank you for checking out part two of this series. The last post went through installing docker on an Ubuntu server to begin our journey to building a CI/CD pipeline. In this post it will be all about connecting Drone to our code repository, in this case GitHub. From our reference diagram below, we will be focusing on the connections from GitHub to the Drone runners.</p>
<p><img src="/blog/images/ci_cd_blog.png" alt="High Level Design"></p>
<h2 id="the-pipeline-server-and-runners">The Pipeline Server and Runners</h2>
<p>Now that we have docker installed, we need something to test our code or execute it. This is one of the main pieces of CI/CD. There are many tools out there that can perform this function. Some of them are Jenkins, Drone, Travis CI, and many more. In this case I went with Drone. Simply because it was new to me and I wanted to play with it. The Drone server interacts with your source control of choice, in my case GitHub. The Drone server and runners can be installed as containers. The runners are actually executing your pipelines. This could be code linting, testing, and deployment of configurations.</p>
<p>One of the neat things about drone is that each runner is an isolated container environment. For example if we wanted to run a pipeline using a python based app, we could pull down a python image and run that as our execution container. Or maybe we are testing some powershell and needed a windows execution environment, we could pull down that image. Once the runners are done with their job, they are destroyed and leave nothing to cleanup.</p>
<h2 id="why-ngrok">Why NGROK?</h2>
<p>From the diagram above you may have seen NGROK and thought, “what the heck is that?”. Since we will be integrating GitHub with Drone, GitHub needs a public URL or address to hit when making webhook calls to the Drone server. This is more than suitable for our testing purposes. NGROK will form a secure tunnel between your node and their service. All requests that hit this URL will then be forwarded to your application or web server for example. Please note NGROK has free and paid versions. The free version is more than what we need for testing.</p>
<h2 id="installing-ngrok">Installing NGROK</h2>
<p>Login to Ubuntu Server and run the following commands:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">mkdir temp <span style="color:#f92672">&amp;&amp;</span> cd temp
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
sudo apt install unzip
unzip ngrok-stable-linux-amd64.zip
./ngrok http <span style="color:#ae81ff">80</span>
</code></pre></div><p>At this point you will see some output similar to below. I didn’t see an option to run NGROK detached, so in the meantime leave this running and connect to the server on another session. Save these URLs as they will be used to build an OAuth application on GitHub.</p>
<p><img src="/blog/images/ngrok_example.png" alt="NGROK Output"></p>
<h2 id="creating-github-oauth-application">Creating GitHub OAuth Application</h2>
<p>Login to GitHub account and go to Settings &gt; Developer Settings &gt; OAuth apps. From there click on the “”New OAuth App” icon.</p>
<p><img src="/blog/images/oauth.png" alt="OAuth"></p>
<p>Remember that URL we had to note down a while ago? Go ahead and fill in the info, example below as reference.</p>
<p><img src="/blog/images/new-fix.png" alt="Registration"></p>
<p>After clicking on “Register application”, click on “Generate a new client secret”. Write down or save those values somewhere. You will need them when setting up the drone server. Please note, if you restart the NGROK service, a new URL will be generated and you will have to update your OAuth application with that URL.</p>
<h2 id="installing-drone-server">Installing Drone Server</h2>
<p>One more credential needs to be created, the shared secret (RPC_SECRET) between the drone server and runners. Execute the command below from the Ubuntu server.</p>
<p><code>Example from drone.io</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">$ openssl rand -hex <span style="color:#ae81ff">16</span>
bea26a2221fd8090ea38720fc445eca6
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker pull drone/drone:2
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker run <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --volume<span style="color:#f92672">=</span>/var/lib/drone:/data <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_GITHUB_CLIENT_ID<span style="color:#f92672">={{</span>DRONE_GITHUB_CLIENT_ID<span style="color:#f92672">}}</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_GITHUB_CLIENT_SECRET<span style="color:#f92672">={{</span>DRONE_GITHUB_CLIENT_SECRET<span style="color:#f92672">}}</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_RPC_SECRET<span style="color:#f92672">={{</span>DRONE_RPC_SECRET<span style="color:#f92672">}}</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_SERVER_HOST<span style="color:#f92672">={{</span>123456.ngrok.io<span style="color:#f92672">}}</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_SERVER_PROTO<span style="color:#f92672">=</span>https <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_USER_CREATE<span style="color:#f92672">=</span>username:<span style="color:#f92672">{{</span>GitHub Username<span style="color:#f92672">}}</span>,admin:true <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --publish<span style="color:#f92672">=</span>80:80 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --publish<span style="color:#f92672">=</span>443:443 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --restart<span style="color:#f92672">=</span>always <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --detach<span style="color:#f92672">=</span>true <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --name<span style="color:#f92672">=</span>drone <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  drone/drone:2
</code></pre></div><p>Feel free to set environment variables or just replace the {{}} settings with the values you have gathered so far. If you want a more in depth breakdown of all the variables, please check out the Drone docs!</p>
<h2 id="installing-drone-runners">Installing Drone Runners</h2>
<p>The runners are interesting, there’s a few versions of the runners depending on what type of environment you are working with. For example, the exec runner will execute pipelines directly on the host, no isolation provided. Useful if you are in an environment that cant utilize containers. This really didn’t seem like a good idea to me especially as I was testing and destroying things all the time. Drone recommends starting with the docker runner if you are new to Drone and I would echo that. For more info on the Drone runner install, check out their <a href="https://docs.drone.io/runner/docker/installation/linux/">docs</a>!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker pull drone/drone-runner-docker:1
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">docker run --detach <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --volume<span style="color:#f92672">=</span>/var/run/docker.sock:/var/run/docker.sock <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_RPC_PROTO<span style="color:#f92672">=</span>https <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_RPC_HOST<span style="color:#f92672">={{</span>123456.ngrok.io<span style="color:#f92672">}}</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_RPC_SECRET<span style="color:#f92672">={{</span>super-duper-secret<span style="color:#f92672">}}</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_RUNNER_CAPACITY<span style="color:#f92672">=</span><span style="color:#ae81ff">3</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --env<span style="color:#f92672">=</span>DRONE_RUNNER_NAME<span style="color:#f92672">=</span>my-first-runner <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --publish<span style="color:#f92672">=</span>3000:3000 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --restart<span style="color:#f92672">=</span>always <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  --name<span style="color:#f92672">=</span>runner <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  drone/drone-runner-docker:1
</code></pre></div><p>Please note, I had issues when running the dynamic URL and Chrome, if you run into this issue please test with Firefox.</p>
<h2 id="welcome-and-activating-a-repository">Welcome and Activating a Repository</h2>
<p>At this point you should be able to hit your public URL, something like “http://1234567.ngrok.io”. You will be greeted with the Drone welcome page. Feel free to login and authenticate with your GitHub account. Now that you are logged in, you may see something like below.</p>
<p><img src="/blog/images/non-active.png" alt="Non active"></p>
<p>No repositories are active and being synced with Drone. If you already have a repository created, feel free to click on it and then click on “ACTIVATE REPOSITORY”. Something like this will then be displayed.</p>
<p><img src="/blog/images/repo-settings.png" alt="Repo Settings"></p>
<p>I left my build fairly vanilla but something that would be interesting is cron jobs from the server. Imagine the main or golden config repository is executed everyday at midnight to prevent configuration drift. We will get into the configuration file in a moment. In the meantime head to “Secrets” under organization or under general.</p>
<p><img src="/blog/images/secrets.png" alt="Secrets"></p>
<p>Lets say our script needs to connect to our devices using a key or password. We can set secrets here that can then be utilized by our build environments. In my lab I will be using Arista devices with simple username and password for authentication. The scripts will then look for an environment variable of “MY_PASS”. Go ahead and save changes once you are happy with them. You probably noticed there is a setting for “Configuration” and it is currently set to <em>.drone.yml</em>. Stayed tuned on the next episode of Dragon Ball… wait a minute that’s not this program. Seriously, on the next part I will go over creating a <em>.drone.yml</em> file and creating our very own docker container image!</p>
<ul>
<li><a href="https://docs.drone.io/">Drone Docs</a></li>
<li><a href="https://ngrok.com/docs">NGROK Docs</a></li>
<li><a href="https://unsplash.com/photos/L9wrEGJjRdo">Featured image by Jonathan Lampel</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/">Building a Network CI/CD Pipeline Part 1</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/">Building a Network CI/CD Pipeline Part 3</a></li>
<li><a href="https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/">Building a Network Ci/CD Pipeline Part 4</a></li>
<li><a href="https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/">Building a Network CI/CD Pipeline Part 5</a></li>
<li><a href="https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/">Building a Network CI/CD Pipeline Part 6</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Building a Network CI/CD Pipeline Part 1</title>
            <link>https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/</link>
            <pubDate>Wed, 20 Oct 2021 10:26:36 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/</guid>
            <description>Introduction Hello all and thank you for joining me on another blog post! In this post or series of posts I hope to walk you through my journey on building a network CI/CD pipeline. This pipeline is not perfect and I’m sure there’s much more efficient ways to produce the same outcomes. I did make the choice to leave out a few things as the technologies involved and complexity kept growing.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/selim.jpg" alt="Featured Image"></p>
<h2 id="introduction">Introduction</h2>
<p>Hello all and thank you for joining me on another blog post! In this post or series of posts I hope to walk you through my journey on building a network CI/CD pipeline. This pipeline is not perfect and I’m sure there’s much more efficient ways to produce the same outcomes. I did make the choice to leave out a few things as the technologies involved and complexity kept growing. For example, I did not go deep into infrastructure as code with this example but I will link a great series from a fellow network engineer at the bottom of this post.</p>
<p>I tried to make this deployment as simple as possible and a few of the technologies are very new to me. I say this because I feel a lot of us get lost in the sauce when so many new technologies are mentioned or even displayed in one architecture diagram. I hope to minimize some of that restraint and break the pieces down as much as possible.</p>
<h2 id="what-is-cicd">What is CI/CD?</h2>
<p>Some of you might be wondering what CI/CD is or even why we should care? CI/CD is a software development concept where software is created, tested and eventually released in a continuous manner. The pieces involved for different applications may differ but I hope to show you some of the main concepts.</p>
<p>How does this relate to network engineering? Lets say you didn’t have a CI/CD pipeline and you wanted to make a change on the production network. The steps would be something like the following:</p>
<ul>
<li>User requests change to the network</li>
<li>Change control ticket is created detailing change and test plan</li>
<li>Login to a test environment if there is one and test the change</li>
<li>Login to the devices one by one and implement change</li>
<li>Validate expected outcomes after change on the devices</li>
</ul>
<p>This process is fairly slow and has a lot of touch points. For example, the testing plan by different engineers may differ or a missed validation could lead to a network outage or an unexpected behavior from the change. Using a CI/CD pipeline we hope to automate a lot of these steps to ensure the change is correct and safe to implement on our network. This isn’t to say that a pipeline will eliminate all errors, far from that. This will allow us to continually improve our testing and the network over time to make it easier to manage and implement changes quickly and safely.</p>
<h2 id="pieces-to-the-puzzle">Pieces to the Puzzle</h2>
<p>Below are some of the technologies we will be breaking down over this series of posts:</p>
<ul>
<li>GitHub</li>
<li>Docker</li>
<li>Drone(pipeline server and runners)</li>
<li>NGROK</li>
<li>Batfish</li>
<li>Nornir/NAPALM</li>
<li>Suzieq</li>
</ul>
<p><img src="/blog/images/ci_cd_blog.png" alt="High Level Design"></p>
<h2 id="getting-started">Getting Started</h2>
<p>Please note, the local code development can be done on any laptop or PC of choice. The docker pieces can be done on a local machine as well but I chose to stand up a simple Ubuntu 20.04 virtual machine with 4 CPUs and 8G of RAM.</p>
<h3 id="github">GitHub</h3>
<p>If you are following along or just getting started then I would highly recommend creating a GitHub account. This is a great place to share and collaborate with other creators in a wide variety of spaces. If you are studying a programming language or going down the path of NetDevOps, you will definitely want some version control.</p>
<h3 id="docker">Docker</h3>
<p>I’ll be honest, I’m fairly new to docker and containers as a whole. Imagine a docker container being one of those Lunchables you ate as a kid. Everything you need is inside of that container. No external dependencies are involved. As mentioned before I created an Ubuntu VM for this deployment to run docker and any containers. The first step was getting docker installed. Credit to the team at Digital Ocean for the great write up on installing docker, check it out <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04">here</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository <span style="color:#e6db74">&#34;deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable&#34;</span>
apt-cache policy docker-ce
sudo apt install docker-ce
sudo systemctl status docker
</code></pre></div><h3 id="using-docker-without-sudo">Using Docker without Sudo</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo usermod -aG docker <span style="color:#e6db74">${</span>USER<span style="color:#e6db74">}</span>
su - <span style="color:#e6db74">${</span>USER<span style="color:#e6db74">}</span>
</code></pre></div><h3 id="useful-docker-commands">Useful Docker Commands</h3>
<p><code>View a list of running containers</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx@drone:~$ docker ps
CONTAINER ID   IMAGE                         COMMAND                  STATUS         NAMES
ed76314f72f7   drone/drone-runner-docker:1   <span style="color:#e6db74">&#34;/bin/drone-runner-d…&#34;</span>   Up <span style="color:#ae81ff">2</span> minutes   runner
500785d2e6d4   drone/drone:2                 <span style="color:#e6db74">&#34;/bin/drone-server&#34;</span>      Up <span style="color:#ae81ff">3</span> minutes   drone
2df0b3ee02fb   batfish/batfish               <span style="color:#e6db74">&#34;java -XX:-UseCompre…&#34;</span>   Up <span style="color:#ae81ff">8</span> days      batfish
</code></pre></div><p><code>Stop or start containers “docker stop/start &lt;container name&gt;</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">juliopdx@drone:~$ docker stop runner
runner
juliopdx@drone:~$ docker start runner
runner
juliopdx@drone:~$
</code></pre></div><p>If you want to explore more Docker commands, check out the <a href="https://docs.docker.com/engine/reference/commandline/docker/">official documentation</a>!</p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>I’ll end this one here. Stay tuned for the next post, setting up the Drone server and runners!</p>
<ul>
<li><a href="https://nwmichl.net/2020/10/28/network-infrastructure-as-code-with-ansible-part-1/">Infrastructure as code series by NWMichl</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04">Installing Docker on Ubuntu 20.04 by Digital Ocean</a></li>
<li><a href="https://unsplash.com/photos/Grg6bwZuBMs">Featured image by @selimarda</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/">Building a Network CI/CD Pipeline Part 2</a></li>
<li><a href="https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/">Building a Network CI/CD Pipeline Part 3</a></li>
<li><a href="https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/">Building a Network Ci/CD Pipeline Part 4</a></li>
<li><a href="https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/">Building a Network CI/CD Pipeline Part 5</a></li>
<li><a href="https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/">Building a Network CI/CD Pipeline Part 6</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>How to Generate Cisco Lifecycle Documentation With Python and APIs</title>
            <link>https://juliopdx.com/2021/09/23/how-to-generate-cisco-lifecycle-documentation-with-python-and-apis/</link>
            <pubDate>Thu, 23 Sep 2021 10:06:16 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/09/23/how-to-generate-cisco-lifecycle-documentation-with-python-and-apis/</guid>
            <description>Introduction I was recently in a Twitter conversation with a few network engineers, in that feed was great dialogue on how often folks upgrade their network environments. Whether its when a massive vulnerability hits or just keeping up with vendor recommended releases. I mentioned that the Cisco Support APIs are an incredible resource. These APIs allow users with the appropriate access to get information about their devices. This could be recommended software releases, EoX dates, last day of support, vulnerabilities, and others.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I was recently in a Twitter conversation with a few network engineers, in that feed was great dialogue on how often folks upgrade their network environments. Whether its when a massive vulnerability hits or just keeping up with vendor recommended releases. I mentioned that the <a href="https://developer.cisco.com/docs/support-apis/#!introduction-to-cisco-support-apis/introduction-to-cisco-support-apis">Cisco Support APIs</a> are an incredible resource. These APIs allow users with the appropriate access to get information about their devices. This could be recommended software releases, EoX dates, last day of support, vulnerabilities, and others.</p>
<p>I wanted to see if there was a way to incorporate a few of these APIs together to create meaningful documentation for the operator in the field. This documentation could then be used to plan out device upgrades, replacements, or just a way to track software versions of the entire device fleet. I hope you find something useful or maybe take bits and pieces so it may help you in your environment.</p>
<h2 id="cisco-support-apis">Cisco Support APIs</h2>
<p>As mentioned earlier, these APIs really are an incredible resource. If you are a Cisco Smart Net Total Care customer or a Cisco Partner Support Services partner, you most likely have access to these APIs. If you would like to follow along you will have to register an application to get started. It sounds more daunting than it really is, check out this <a href="https://developer.cisco.com/docs/support-apis/#!application-registration/application-registration">guide</a> to get that process started.</p>
<h2 id="handling-support-api-authentication">Handling Support API Authentication</h2>
<p>When interacting with APIs there are usually a few options to choose from. You may use a username/password combination, token, or a combination of client IDs and secrets. I chose the latter. The default timer for tokens used for the support APIs is good for about one hour. This will require the user to reauthenticate every hour. I tried to keep this simple by having a local credentials.yaml file in the repo, which is then added to the <em>.gitignore</em> file.</p>
<p><code>credentials.yaml example</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">support_client_id</span>: <span style="color:#ae81ff">SomeClientId</span>
<span style="color:#f92672">support_client_secret</span>: <span style="color:#ae81ff">SomeClientSecret</span>
</code></pre></div><p>Once you have the client ID and client secret from the app that is registered. You can execute the auth.py script in the repo. Repository is linked at the bottom of this post!</p>
<p><code>auth.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> requests
<span style="color:#f92672">import</span> yaml


URL <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://cloudsso.cisco.com/as/token.oauth2&#34;</span>

<span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;./credentials.yaml&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> file:
    myvars <span style="color:#f92672">=</span> yaml<span style="color:#f92672">.</span>safe_load(file)

payload <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;grant_type=client_credentials&amp;client_id=</span><span style="color:#e6db74">{</span>myvars[<span style="color:#e6db74">&#39;support_client_id&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;client_secret=</span><span style="color:#e6db74">{</span>myvars[<span style="color:#e6db74">&#39;support_client_secret&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>

headers <span style="color:#f92672">=</span> {
    <span style="color:#e6db74">&#34;Content-Type&#34;</span>: <span style="color:#e6db74">&#34;application/x-www-form-urlencoded&#34;</span>,
}

response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>request(<span style="color:#e6db74">&#34;POST&#34;</span>, URL, headers<span style="color:#f92672">=</span>headers, data<span style="color:#f92672">=</span>payload)

<span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
    <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;credentials.yaml&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> f:
        creds <span style="color:#f92672">=</span> yaml<span style="color:#f92672">.</span>load(f, Loader<span style="color:#f92672">=</span>yaml<span style="color:#f92672">.</span>SafeLoader)

    creds[<span style="color:#e6db74">&#34;support_auth_token&#34;</span>] <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">&#34;access_token&#34;</span>]

    <span style="color:#75715e"># Rewriting credentials.yaml with new token variable</span>
    <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;credentials.yaml&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> f:
        yaml<span style="color:#f92672">.</span>dump(creds, f)
        print(<span style="color:#e6db74">&#34;Auth token updated successfully!!!&#34;</span>)
<span style="color:#66d9ef">else</span>:
    print(<span style="color:#e6db74">&#34;Failed to update token, please check client ID or client secret&#34;</span>)
</code></pre></div><p>At the top of the script we are importing requests to interact with APIs and yaml for working with yaml files. This will then load the current client secret and client ID from our credentials.yaml file. On line 16 we run our API POST call to retrieve a token. This token can then be used to interact with the support APIs. The second half of the script will essentially recreate the <em>credentials.yaml</em> file, but with the token now in place. Example below!</p>
<p><code>credentials.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">support_auth_token</span>: <span style="color:#ae81ff">SomeToken</span>
<span style="color:#f92672">support_client_id</span>: <span style="color:#ae81ff">SomeClientId</span>
<span style="color:#f92672">support_client_secret</span>: <span style="color:#ae81ff">SomeClientSecret</span>
</code></pre></div><h2 id="chicken-and-egg-problem">Chicken and Egg Problem</h2>
<p>Now that we have a working token, we could use this to run some kind of API GET to gather some data about a device or platform based on the serial number or part ID. Here is the main issue I ran into. The support APIs require either a serial number or part ID to gather relevant information. At some point you will need an inventory or a list of serial numbers to feed into the APIs to gather information for you. I am cheating a bit and started with an inventory file of three devices. This inventory only included the device IP addresses and their platform for handling connections. Example inventory below.</p>
<p><code>hosts.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#75715e"># Please note, the cisco_ios group is only used to set the device platform type</span>
<span style="color:#f92672">some-router-01</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#e6db74">&#34;192.168.10.10&#34;</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">cisco_ios</span>

<span style="color:#f92672">some-router-02</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#e6db74">&#34;192.168.10.11&#34;</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">cisco_ios</span>

<span style="color:#f92672">some-other-node</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#e6db74">&#34;192.168.10.12&#34;</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">cisco_ios</span>
</code></pre></div><h2 id="imports-and-csvs">Imports and CSVs</h2>
<p>We will be using Nornir and NAPALM for the initial connection to our devices. The csv import is used for creating our file with all of our information. The yaml import is used to interact with our credentials.yaml file and load the token. You may notice a lot of imports from “tools”, these are a few neat functions I made for this small project. More on those in a bit.</p>
<p><code>info.py imports and auth</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> csv
<span style="color:#f92672">import</span> yaml
<span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_get
<span style="color:#f92672">from</span> tools <span style="color:#f92672">import</span> manu_year_cisco, nornir_set_creds, product_info, software_release, eox


<span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;./credentials.yaml&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> file:
    myvars <span style="color:#f92672">=</span> yaml<span style="color:#f92672">.</span>safe_load(file)

my_token <span style="color:#f92672">=</span> myvars[<span style="color:#e6db74">&#34;support_auth_token&#34;</span>]
</code></pre></div><p>Moving a bit down on the script is the initial interaction with a csv file. We will use the CSV library to create our file and write a few headers that will soon be populated with relevant information.</p>
<p><code>info.py snippet</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;device_info.csv&#34;</span>, mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;w&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> csv_file:
    fieldnames <span style="color:#f92672">=</span> [
        <span style="color:#e6db74">&#34;Name&#34;</span>,
        <span style="color:#e6db74">&#34;Platform&#34;</span>,
        <span style="color:#e6db74">&#34;Model&#34;</span>,
        <span style="color:#e6db74">&#34;Base_PID&#34;</span>,
        <span style="color:#e6db74">&#34;Replacement&#34;</span>,
        <span style="color:#e6db74">&#34;Serial&#34;</span>,
        <span style="color:#e6db74">&#34;EoS&#34;</span>,
        <span style="color:#e6db74">&#34;EoSM&#34;</span>,
        <span style="color:#e6db74">&#34;LDoS&#34;</span>,
        <span style="color:#e6db74">&#34;EoCR&#34;</span>,
        <span style="color:#e6db74">&#34;Manufacture Year&#34;</span>,
        <span style="color:#e6db74">&#34;Current SW&#34;</span>,
        <span style="color:#e6db74">&#34;Recommended SW&#34;</span>,
    ]
    writer <span style="color:#f92672">=</span> csv<span style="color:#f92672">.</span>DictWriter(csv_file, fieldnames<span style="color:#f92672">=</span>fieldnames)
    writer<span style="color:#f92672">.</span>writeheader()
</code></pre></div><h2 id="nornir-and-napalm">Nornir and NAPALM</h2>
<p>This script utilizes Nornir and NAPALM for the initial device connection. This is only used for a very small run to use a NAPALM getter. In this case we are using the “get_facts” getter. This will retrieve a ton of useful information. For my case I will be capturing the hostname, model, version, serial, and platform. Snippet below of this process.</p>
<p><code>info.py snipper</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">all_the_things</span>(task):
    <span style="color:#e6db74">&#34;&#34;&#34;It does all the things&#34;&#34;&#34;</span>
    task1_result <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Get facts for </span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">!&#34;</span>, task<span style="color:#f92672">=</span>napalm_get, getters<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;get_facts&#34;</span>]
    )

    name <span style="color:#f92672">=</span> task1_result[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>result[<span style="color:#e6db74">&#34;get_facts&#34;</span>][<span style="color:#e6db74">&#34;hostname&#34;</span>]
    model <span style="color:#f92672">=</span> task1_result[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>result[<span style="color:#e6db74">&#34;get_facts&#34;</span>][<span style="color:#e6db74">&#34;model&#34;</span>]
    ver <span style="color:#f92672">=</span> task1_result[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>result[<span style="color:#e6db74">&#34;get_facts&#34;</span>][<span style="color:#e6db74">&#34;os_version&#34;</span>]
    version <span style="color:#f92672">=</span> ver<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;,&#34;</span>)[<span style="color:#ae81ff">1</span>]
    serial <span style="color:#f92672">=</span> task1_result[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>result[<span style="color:#e6db74">&#34;get_facts&#34;</span>][<span style="color:#e6db74">&#34;serial_number&#34;</span>]
    platform <span style="color:#f92672">=</span> task1_result[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>result[<span style="color:#e6db74">&#34;get_facts&#34;</span>][<span style="color:#e6db74">&#34;vendor&#34;</span>]
</code></pre></div><h2 id="support-apis-and-functions">Support APIs and Functions</h2>
<p>Now that we have the device serial number and model, we can utilize the support APIs to get any other relevant information. When I initially started getting my thoughts into code, the script started getting rather long. Too long for the actual process I was implementing. I eventually split out the API calls to functions within the “tools.py” file. Remember all those imports? I’ll go over one that sets the credentials (thanks Kirk Byers) and one that interacts with the support APIs. The support APIs all follow the same general flow.</p>
<p>I was having a bit of trouble elegantly setting the device username and password without being in any file. I eventually saw a simple function by Kirk, one of the creators of Nornir.</p>
<p><code>info.py main</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Used to run all the things&#34;&#34;&#34;</span>
    norn <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;config/config.yaml&#34;</span>)
    nornir_set_creds(norn)
    norn<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>all_the_things)
<span style="color:#75715e"># Nornir is initialized as &#34;norn&#34;</span>
<span style="color:#75715e"># The &#34;norn&#34; object is then passed into the nornir_set_creds function</span>
</code></pre></div><p>The version I went with will look for environment variables to be set, you could set this to prompt as well but I got tired of typing.</p>
<p><code>Setting Credentials</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">nornir_set_creds</span>(norn, username<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>, password<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>):
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Handler so credentials are not stored in cleartext.
</span><span style="color:#e6db74">    Thank you Kirk!
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> username:
        username <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;NORNIR_USER&#34;</span>)
    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> password:
        password <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;NORNIR_PASS&#34;</span>)

    <span style="color:#66d9ef">for</span> host_obj <span style="color:#f92672">in</span> norn<span style="color:#f92672">.</span>inventory<span style="color:#f92672">.</span>hosts<span style="color:#f92672">.</span>values():
        host_obj<span style="color:#f92672">.</span>username <span style="color:#f92672">=</span> username
        host_obj<span style="color:#f92672">.</span>password <span style="color:#f92672">=</span> password
</code></pre></div><p>I’ll do my best to breakdown the most involved function for the support APIs, EoX! This API returns so much useful information that I made the function just return a dictionary of useful things that can be passed to our CSV. The function takes a serial number as the first parameter and a token as the second. These values will then be passed to the headers of the API call as well as the URL. I run a simple check to see if there’s even been an announcement for that serial number. If not, then set all the values to “N/A”. It gets really interesting after the else statement. You probably notice “Dq” all over the place. “Dq”, short for dictionary query, allows you to query a dictionary in really useful ways. This tool comes from the popular <a href="https://pubhub.devnetcloud.com/media/genie-docs/docs/userguide/utils/index.html#">Genie library</a>. In all honesty, I just really wanted to play with it! It really does make finding relevant information in a dictionary a lot shorter. I’ll give you an example after the script.</p>
<p><code>eox function</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">eox</span>(serial: str, token: str):
    <span style="color:#e6db74">&#34;&#34;&#34;Returns all eox information and replacement option from serial number&#34;&#34;&#34;</span>
    device <span style="color:#f92672">=</span> {}
    headers <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;Authorization&#34;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Bearer </span><span style="color:#e6db74">{</span>token<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>}
    url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;https://api.cisco.com/supporttools/eox/rest/5/EOXBySerialNumber/1/</span><span style="color:#e6db74">{</span>serial<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
    req <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>request(<span style="color:#e6db74">&#34;GET&#34;</span>, url, headers<span style="color:#f92672">=</span>headers)
    content <span style="color:#f92672">=</span> req<span style="color:#f92672">.</span>json()
    <span style="color:#66d9ef">if</span> content[<span style="color:#e6db74">&#34;EOXRecord&#34;</span>][<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;EOXExternalAnnouncementDate&#34;</span>][<span style="color:#e6db74">&#34;value&#34;</span>] <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span>:
        device[<span style="color:#e6db74">&#34;eos&#34;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;N/A&#34;</span>
        device[<span style="color:#e6db74">&#34;eosm&#34;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;N/A&#34;</span>
        device[<span style="color:#e6db74">&#34;ldos&#34;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;N/A&#34;</span>
        device[<span style="color:#e6db74">&#34;eocr&#34;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;N/A&#34;</span>
        device[<span style="color:#e6db74">&#34;replacement&#34;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;N/A&#34;</span>
    <span style="color:#66d9ef">else</span>:
        device[<span style="color:#e6db74">&#34;eos&#34;</span>] <span style="color:#f92672">=</span> Dq(content)<span style="color:#f92672">.</span>contains(<span style="color:#e6db74">&#34;EndOfSaleDate&#34;</span>)<span style="color:#f92672">.</span>get_values(<span style="color:#e6db74">&#34;value&#34;</span>, <span style="color:#ae81ff">0</span>)
        device[<span style="color:#e6db74">&#34;eosm&#34;</span>] <span style="color:#f92672">=</span> (
            Dq(content)<span style="color:#f92672">.</span>contains(<span style="color:#e6db74">&#34;EndOfSWMaintenanceReleases&#34;</span>)<span style="color:#f92672">.</span>get_values(<span style="color:#e6db74">&#34;value&#34;</span>, <span style="color:#ae81ff">0</span>)
        )
        device[<span style="color:#e6db74">&#34;ldos&#34;</span>] <span style="color:#f92672">=</span> (
            Dq(content)<span style="color:#f92672">.</span>contains(<span style="color:#e6db74">&#34;LastDateOfSupport&#34;</span>)<span style="color:#f92672">.</span>get_values(<span style="color:#e6db74">&#34;value&#34;</span>, <span style="color:#ae81ff">0</span>)
        )
        device[<span style="color:#e6db74">&#34;eocr&#34;</span>] <span style="color:#f92672">=</span> (
            Dq(content)<span style="color:#f92672">.</span>contains(<span style="color:#e6db74">&#34;EndOfServiceContractRenewal&#34;</span>)<span style="color:#f92672">.</span>get_values(<span style="color:#e6db74">&#34;value&#34;</span>, <span style="color:#ae81ff">0</span>)
        )
        device[<span style="color:#e6db74">&#34;replacement&#34;</span>] <span style="color:#f92672">=</span> (
            Dq(content)
            <span style="color:#f92672">.</span>contains(<span style="color:#e6db74">&#34;MigrationProductId&#34;</span>)
            <span style="color:#f92672">.</span>get_values(<span style="color:#e6db74">&#34;MigrationProductId&#34;</span>, <span style="color:#ae81ff">0</span>)
        )
    <span style="color:#66d9ef">return</span> device
</code></pre></div><h2 id="dq-example">Dq Example</h2>
<p>Lets say we had the following dictionary to parse through. Our goal is to get the state value of the “10.10.10.10” OSPF neighbor.</p>
<p><code>Example dictionary</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> device <span style="color:#f92672">=</span> {
<span style="color:#f92672">...</span>     <span style="color:#e6db74">&#34;router&#34;</span>: {
<span style="color:#f92672">...</span>         <span style="color:#e6db74">&#34;ospf&#34;</span>: {
<span style="color:#f92672">...</span>             <span style="color:#e6db74">&#34;neighbors&#34;</span>: [
<span style="color:#f92672">...</span>                 {
<span style="color:#f92672">...</span>                     <span style="color:#e6db74">&#34;10.10.10.10&#34;</span>: {
<span style="color:#f92672">...</span>                         <span style="color:#e6db74">&#34;state&#34;</span>: <span style="color:#e6db74">&#34;up&#34;</span>
<span style="color:#f92672">...</span>                     }
<span style="color:#f92672">...</span>                 },
<span style="color:#f92672">...</span>                 {
<span style="color:#f92672">...</span>                     <span style="color:#e6db74">&#34;20.20.20.20&#34;</span>: {
<span style="color:#f92672">...</span>                         <span style="color:#e6db74">&#34;state&#34;</span>: <span style="color:#e6db74">&#34;down&#34;</span>
<span style="color:#f92672">...</span>                     }
<span style="color:#f92672">...</span>                 }
<span style="color:#f92672">...</span>             ]
<span style="color:#f92672">...</span>         }
<span style="color:#f92672">...</span>     }
<span style="color:#f92672">...</span> }
<span style="color:#f92672">&gt;&gt;&gt;</span> device[<span style="color:#e6db74">&#34;router&#34;</span>][<span style="color:#e6db74">&#34;ospf&#34;</span>][<span style="color:#e6db74">&#34;neighbors&#34;</span>][<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;10.10.10.10&#34;</span>][<span style="color:#e6db74">&#34;state&#34;</span>]
<span style="color:#e6db74">&#39;up&#39;</span>
</code></pre></div><p>What you see above is one way of doing it. Mind you, in my example this dictionary is only going so many levels deep. Imagine if you were going down a deeply nested dictionary. Below is how you would do the same using Dq.</p>
<p><code>Dq Example</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> Dq(device)<span style="color:#f92672">.</span>contains(<span style="color:#e6db74">&#34;10.10.10.10&#34;</span>)<span style="color:#f92672">.</span>get_values(<span style="color:#e6db74">&#34;state&#34;</span>)
[<span style="color:#e6db74">&#39;up&#39;</span>]
<span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#75715e"># Notice that Dq returns a list, below is how to get only the string</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> Dq(device)<span style="color:#f92672">.</span>contains(<span style="color:#e6db74">&#34;10.10.10.10&#34;</span>)<span style="color:#f92672">.</span>get_values(<span style="color:#e6db74">&#34;state&#34;</span>, <span style="color:#ae81ff">0</span>)
<span style="color:#e6db74">&#39;up&#39;</span>
</code></pre></div><h2 id="send-data-to-csv">Send Data to CSV</h2>
<p>Now that we execute all of our functions to hit the support APIs, we have all the information we need to build our CSV. At the top we are setting variables to the result of what is returned from our functions. Some functions only require a serial, like calculating the manufacture year, and others require a serial number and token. After that we use the CSV library to write all the data to the file we created earlier, in this call we are opening the file in append mode with the <code>mode=’a'</code> set.</p>
<p><code>info.py snippet</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">    manu_year <span style="color:#f92672">=</span> manu_year_cisco(serial)
    base_pid <span style="color:#f92672">=</span> product_info(serial, my_token)
    upgrade <span style="color:#f92672">=</span> software_release(base_pid, my_token)
    my_eox <span style="color:#f92672">=</span> eox(serial, my_token)

    <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;device_info.csv&#34;</span>, mode<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;a&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> myfile:
        write <span style="color:#f92672">=</span> csv<span style="color:#f92672">.</span>DictWriter(myfile, fieldnames<span style="color:#f92672">=</span>fieldnames)
        write<span style="color:#f92672">.</span>writerow(
            {
                <span style="color:#e6db74">&#34;Name&#34;</span>: name,
                <span style="color:#e6db74">&#34;Model&#34;</span>: model,
                <span style="color:#e6db74">&#34;Current SW&#34;</span>: version,
                <span style="color:#e6db74">&#34;Serial&#34;</span>: serial,
                <span style="color:#e6db74">&#34;Platform&#34;</span>: platform,
                <span style="color:#e6db74">&#34;Manufacture Year&#34;</span>: manu_year,
                <span style="color:#e6db74">&#34;Base_PID&#34;</span>: base_pid,
                <span style="color:#e6db74">&#34;Recommended SW&#34;</span>: upgrade,
                <span style="color:#e6db74">&#34;EoS&#34;</span>: my_eox[<span style="color:#e6db74">&#34;eos&#34;</span>],
                <span style="color:#e6db74">&#34;EoSM&#34;</span>: my_eox[<span style="color:#e6db74">&#34;eosm&#34;</span>],
                <span style="color:#e6db74">&#34;LDoS&#34;</span>: my_eox[<span style="color:#e6db74">&#34;ldos&#34;</span>],
                <span style="color:#e6db74">&#34;EoCR&#34;</span>: my_eox[<span style="color:#e6db74">&#34;eocr&#34;</span>],
                <span style="color:#e6db74">&#34;Replacement&#34;</span>: my_eox[<span style="color:#e6db74">&#34;replacement&#34;</span>],
            }
        )
</code></pre></div><h2 id="example-csv">Example CSV</h2>
<p>Below is a snip from the CSV example in the repo. If you want to test this out, feel free to clone down the repository and modify your hosts file.</p>
<p><img src="/blog/images/support_info_table.png" alt="Support Info Table"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far! This was a really fun tool to create and allowed me to learn a bit about the support APIs, working with CSVs (with Python), and using Dq! I hope you found this even a bit useful. Something that could be a possibility with a script like this is populating a tool like Netbox!</p>
<ul>
<li><a href="https://unsplash.com/photos/EG2KcQn28RI">Featured Image by Mille Sanders</a></li>
<li><a href="https://github.com/JulioPDX/auto_cisco_support_info">Git Repository</a></li>
<li><a href="https://developer.cisco.com/docs/support-apis/#!introduction-to-cisco-support-apis">Information about the Cisco Support APIs</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Converting Network Icons for Labs With Python</title>
            <link>https://juliopdx.com/2021/09/17/converting-network-icons-for-labs-with-python/</link>
            <pubDate>Fri, 17 Sep 2021 09:31:09 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/09/17/converting-network-icons-for-labs-with-python/</guid>
            <description>Introduction First of all, the diagrams used in this post and repository were originally created by ecceman. Please head over to the original repository and show some love by giving it a star! When going through studies or testing a solution, I tend to lab a lot. On top of that I usually try and make my labs and topologies look pretty. I like to tell myself if the topology looks good, then I will learn more effectively.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/topo_wasif_bhatti.png" alt="Topo Wasif Bhatti"></p>
<h2 id="introduction">Introduction</h2>
<p>First of all, the diagrams used in this post and repository were originally created by ecceman. Please head over to the original <a href="https://github.com/ecceman/affinity">repository</a> and show some love by giving it a star! When going through studies or testing a solution, I tend to lab a lot. On top of that I usually try and make my labs and topologies look pretty. I like to tell myself if the topology looks good, then I will learn more effectively. Example of the icons are above in the featured image.</p>
<p>I found eccemans repository after watching this video from <a href="https://www.youtube.com/watch?v=vUWjkEHPL3A&amp;ab_channel=TheNetworkBerg">The Network Berg</a>. Check out the video as they demonstrate the proper folder location where images can be uploaded on an EVE-NG server. I’ll also post a small example in this post. The original files are in SVG format, whereas we need PNG files for EVE-NG.</p>
<h2 id="manual-conversion">Manual Conversion</h2>
<p>At first the workflow would be something like the following:</p>
<ul>
<li>Clone the repository</li>
<li>Convert image from SVG to PNG</li>
<li>Right size image to 52×52</li>
</ul>
<p>Imagine repeating this process 10 times or even a couple hundred times. You can see how this would be pretty annoying after a while. I recall someone mentioning to me once, the best way to learn a programming language or any tool is to actually put it to use. Force the mind into a real world problem and use what has been learned to develop a solution.</p>
<h2 id="automated-conversion">Automated Conversion</h2>
<p>I’ll be completely honest, some of the libraries used were a first for me. But that points to the power of programming languages. Once you learn the basics, with a bit of reading and examples, one can develop very strong solutions. I’ll try and walkthrough the process of developing a simple python script that became really helpful for me.</p>
<h3 id="research">Research</h3>
<p>I started where most of us start. Just heading to a web browser and typing “how to convert SVG to PNG using python”. While there were a lot of decent examples, folks new to programming may find some of them overwhelming. Luckily for you and I, stack overflow is a thing and the community usually post awesome answers to questions like the one I had. If you would like to check out the post on stack, check it out <a href="https://stackoverflow.com/questions/51450134/how-to-convert-svg-to-png-or-jpeg-in-python">here</a>!</p>
<h3 id="libvips-and-pyvips">libvips and pyvips</h3>
<p>I wont go too much into detail on these libraries. The short of it is, libvips is an image processing library that can do a heck of a lot more than what I’m using it for. If you would like to check out their docs, <a href="https://www.libvips.org/API/current/">click here</a>. I like to think of pyvips as an entry point that leverages libvips under the hood. Write program in python and then it will call functions from libvips to execute.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo apt-get install libvips
pip3 install pyvips
</code></pre></div><h3 id="interating-over-folders-and-files">Interating Over Folders and Files</h3>
<p>Now that I had tools that looked promising for our end goal, I needed a way to iterate over all folders and SVG files within them. Initially I figured the script could just create a PNG folder under the shape and color. This way users could go to svg &gt; circle &gt; blue &gt; PNG, and all the PNG files would be available for circle icons that were blue. Below is an example of what I had to work with. Please note this is just a snippet.</p>
<p><code>SVG Folder Structure</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">├── svg
│   ├── circle
│   │   ├── blue
│   │   │   ├── c_bug.svg
│   │   │   ├── c_camera_blue.svg
│   │   │   ├── c_camera_dome_blue.svg
│   │   ├── gray
│   │   │   ├── c_bug.svg
│   │   │   ├── c_camera.svg
│   │   │   ├── c_camera_dome.svg
│   │   ├── green
│   │   │   ├── c_bug_green.svg
│   │   │   ├── c_camera_dome_green.svg
│   │   │   ├── c_camera_green.svg
│   │   └── red
│   │       ├── c_bug_red.svg
│   │       ├── c_camera_dome_red.svg
│   │       ├── c_camera_red.svg
│   ├── naked
│   │   ├── bug.svg
│   │   ├── camera.svg
│   │   ├── camera_dome.svg
│   └── square
│       ├── blue
│       │   ├── sq_bug_blue.svg
│       │   ├── sq_camera_blue.svg
│       │   ├── sq_camera_dome_blue.svg
│       ├── gray
│       │   ├── sq_bug.svg
│       │   ├── sq_camera.svg
│       │   ├── sq_camera_dome.svg
│       ├── green
│       │   ├── sq_bug_green.svg
│       │   ├── sq_camera_dome_green.svg
│       │   ├── sq_camera_green.svg
│
│       └── red
│           ├── sq_bug_red.svg
│           ├── sq_camera_dome_red.svg
│           ├── sq_camera_red.svg
</code></pre></div><p>Iterating over this structure initially seemed very complex but python makes this fairly simple. I leveraged <a href="https://www.geeksforgeeks.org/os-walk-python/">“walk” from “os”</a>. This was a bit of trial and error but when I was able to print the correct files, I could proceed with converting them. Small example below of printing root and files. I pass in a PATH variable to walk as a starting point.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#f92672">from</span> os <span style="color:#f92672">import</span> walk
<span style="color:#f92672">&gt;&gt;&gt;</span> PATH <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;svg/&#34;</span>
<span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#66d9ef">for</span> (root, dirs, files) <span style="color:#f92672">in</span> walk(PATH):
<span style="color:#f92672">...</span>     <span style="color:#66d9ef">for</span> f <span style="color:#f92672">in</span> files:
<span style="color:#f92672">...</span>         <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;.svg&#34;</span> <span style="color:#f92672">in</span> f:
<span style="color:#f92672">...</span>             print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
<span style="color:#f92672">...</span>
svg<span style="color:#f92672">/</span>circle<span style="color:#f92672">/</span>blue<span style="color:#f92672">/</span>c_bug<span style="color:#f92672">.</span>svg
svg<span style="color:#f92672">/</span>circle<span style="color:#f92672">/</span>blue<span style="color:#f92672">/</span>c_camera_blue<span style="color:#f92672">.</span>svg
svg<span style="color:#f92672">/</span>circle<span style="color:#f92672">/</span>blue<span style="color:#f92672">/</span>c_camera_dome_blue<span style="color:#f92672">.</span>svg
svg<span style="color:#f92672">/</span>circle<span style="color:#f92672">/</span>blue<span style="color:#f92672">/</span>c_client_blue<span style="color:#f92672">.</span>svg
svg<span style="color:#f92672">/</span>circle<span style="color:#f92672">/</span>blue<span style="color:#f92672">/</span>c_client_vm_blue<span style="color:#f92672">.</span>svg
</code></pre></div><p>If you notice, root is the same for all the files shown above, “svg/circle/blue/”. All that I needed now from my initial thinking was to add a PNG folder at the end of that root. Then all the shapes and colors would have a PNG folder for the images to be converted. Please see one solution below.</p>
<p><code>Creating PNG Folder</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">try</span>:
    mkdir(path<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/png/&#34;</span>)
<span style="color:#66d9ef">except</span> <span style="color:#a6e22e">OSError</span> <span style="color:#66d9ef">as</span> error:
    <span style="color:#66d9ef">pass</span>
</code></pre></div><p>This isn’t the best solution at all or even the most efficient. This is basically using “mkdir” to create a directory under the root and adding png. The negative here is that it will throw an error after the first time it is created. I just handle it by setting the script to continue after running into this error.</p>
<h3 id="converting-all-the-things">Converting All the Things</h3>
<p>The conversion wasnt too bad at all. I’ll explain the code upfront this time. pyvips has a function to create a thumbnail based off of a file. In this case we are looping over every single file that ends with “.svg”. We size the thumbnail as a height and width of 52. I believe these are recommended from the EVE-NG import icons guide. After that we write the file, when doing this we will place each new file under the root we are iterating over, for example “svg/circle/blue/”, appending “/png/” and ending it by changing the extension name from “svg” to “png”. Clear as mud right?</p>
<p><code>Itsy Bitys Function</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():

    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    All the action
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    <span style="color:#66d9ef">for</span> (root, dirs, files) <span style="color:#f92672">in</span> walk(PATH):
        <span style="color:#66d9ef">for</span> f <span style="color:#f92672">in</span> files:
            <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;.svg&#34;</span> <span style="color:#f92672">in</span> f:
                <span style="color:#66d9ef">try</span>:
                    mkdir(path<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/png/&#34;</span>)
                <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">OSError</span> <span style="color:#66d9ef">as</span> error:
                    <span style="color:#66d9ef">pass</span>
                convert_file <span style="color:#f92672">=</span> pyvips<span style="color:#f92672">.</span>Image<span style="color:#f92672">.</span>thumbnail(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>, <span style="color:#ae81ff">52</span>, height<span style="color:#f92672">=</span><span style="color:#ae81ff">52</span>)
                convert_file<span style="color:#f92672">.</span>write_to_file(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/png/</span><span style="color:#e6db74">{</span>f<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#39;svg&#39;</span>, <span style="color:#e6db74">&#39;png&#39;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</code></pre></div><h2 id="importing-to-eve-ng">Importing to EVE-NG</h2>
<p>At this point all the SVG files have been converted to PNG format. If you would like to add these icons to EVE-NG, you can use something like <a href="https://winscp.net/eng/index.php">winscp</a> or build some automation to send them to your EVE-NG server. The path you will need to store them in is “/opt/unetlab/html/images/icons/”.</p>
<h2 id="bonus-automating-the-reference-file">Bonus, Automating the Reference File</h2>
<p>Something that I thought was interesting, at the time there was no overall view of every icon. Or some type of directory to check out what each icon looked like, unless you opened each file individually. Since that time ecceman has added a nice PNG that displays all the icons.</p>
<p>Before this existed a fellow networker also had a similar idea. Many thanks to <a href="https://twitter.com/pstavirs">@pstavirs</a> for the push!</p>
<p><img src="/blog/images/request.png" alt="Request from pstavirs"></p>
<p>I’ve worked with markdown before and adding image URLs. At this point we had every PNG file sized and ready to go. Why not just write a script that could build this markdown file for us. I ended up using “walk” again since I now needed to loop over every single PNG image. We decided on three icons per row, with name on the right side of the icon. I played with a bit of simple math in the loop to tell the program when to drop to the next line. If you have any questions on the process feel free to ping me. If you want to see the file in the raw <a href="https://raw.githubusercontent.com/JulioPDX/affinity/master/reference.md">click here</a>, crazy right? Rendered version below!</p>
<p><img src="/blog/images/ref_mark.png" alt="Reference File Rendered"></p>
<p><code>ref_build.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():

    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    All the action
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;reference.md&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> my_file:
        <span style="color:#75715e"># Headers and settings alignments for file</span>
        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;# Reference for Icons</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;|Icon|Name|Icon|Name|Icon|Name|</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;|:---:|:---:|:---:|:---:|:---:|:---:|</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#66d9ef">for</span> (root, dirs, files) <span style="color:#f92672">in</span> walk(PATH):
            count <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
            <span style="color:#75715e"># Iterating over all files with .png extension</span>
            <span style="color:#66d9ef">for</span> f <span style="color:#f92672">in</span> files:
                <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;.png&#34;</span> <span style="color:#f92672">in</span> f:
                    <span style="color:#75715e"># Simple math checks on matches and reset</span>
                    <span style="color:#75715e"># counter on third entry</span>
                    count <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
                    <span style="color:#66d9ef">if</span> count <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>:
                        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;|![</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">](</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">)|</span><span style="color:#e6db74">{</span>f<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#39;.png&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">|&#34;</span>)
                    <span style="color:#66d9ef">if</span> count <span style="color:#f92672">==</span> <span style="color:#ae81ff">2</span>:
                        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;![</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">](</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">)|</span><span style="color:#e6db74">{</span>f<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#39;.png&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">|&#34;</span>)
                    <span style="color:#66d9ef">if</span> count <span style="color:#f92672">==</span> <span style="color:#ae81ff">3</span>:
                        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;![</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">](</span><span style="color:#e6db74">{</span>root<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>f<span style="color:#e6db74">}</span><span style="color:#e6db74">)|</span><span style="color:#e6db74">{</span>f<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#39;.png&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">|</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
                        count <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
        my_file<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you all for reading this far, it really means a lot. This was probably one of the most asked questions I get, “How do you get those icons in your topology?”. If you would like to add these images to your EVE instance, feel free to clone down the repository and add whatever you like. Putting what you learn to use has many advantages and this one was a lot of fun. Shout out to <a href="https://twitter.com/Network_101010">Jordi</a>, Wasif, and <a href="https://twitter.com/pstavirs">Srivats</a>. Thank you for being so positive in the community! I wish everyone all the best on your future studies and happy labbing!</p>
<p><img src="/blog/images/happy_community.png" alt="Happy Community"></p>
<ul>
<li><a href="https://github.com/ecceman/affinity">Original repository from ecceman, amazing!</a></li>
<li><a href="https://github.com/JulioPDX/affinity">Fork of original repo to add conversion script and reference builder</a></li>
<li>Featured Image credit to Wasif Bhatti, thank you!</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Integrating Nornir With FastAPI</title>
            <link>https://juliopdx.com/2021/09/01/integrating-nornir-with-fastapi/</link>
            <pubDate>Wed, 01 Sep 2021 18:52:28 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/09/01/integrating-nornir-with-fastapi/</guid>
            <description>Introduction I recently saw a fellow engineer share an amazing article on Real Python. It was a post by Sebastián Ramírez, the creator of FastAPI. I’m fairly comfortable working with APIs but I’ve never even thought about making one. The prospect seems so daunting, especially when I’m still trying to master the foundational skills of python. FastAPI does a ton of heavy lifting to make this process incredibly easy to get started and build web APIs.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I recently saw a fellow engineer share an amazing article on <a href="https://realpython.com/fastapi-python-web-apis/">Real Python</a>. It was a post by Sebastián Ramírez, the creator of FastAPI. I’m fairly comfortable working with APIs but I’ve never even thought about making one. The prospect seems so daunting, especially when I’m still trying to master the foundational skills of python. FastAPI does a ton of heavy lifting to make this process incredibly easy to get started and build web APIs.</p>
<p>When learning something new I try and incorporate it to something that interest me. A decent amount of the time it relates to network engineering. Another example could be learning how to interact with a <a href="https://pokeapi.co/">Pokemon API</a>. I wanted to see if I could possibly integrate Nornir with FastAPI.</p>
<h2 id="objective">Objective</h2>
<p>My initial thinking for this experiment was to use Nornir and NAPALM to connect to devices and retrieve information. If this could then be linked to separate URLs, that would make for a pretty slick way of interacting with networking devices. I really thought I would be making some script that was much much longer than what I actually implemented.</p>
<p>I wont go too deep into Nornir or FastAPI. The Real Python article linked is an amazing resource as well as the FastAPI documentation. I’ve written about Nornir before, and if you are curious about the structure, feel free to check out one of my previous <a href="https://juliopdx.com/2021/02/27/network-validation-with-nornir-napalm/">posts</a>.</p>
<h2 id="topology">Topology</h2>
<p><img src="/blog/images/eve_topo_multi.png" alt="EVE Topo Multi"></p>
<p>The topology consists of three networking nodes from three popular vendors. I tried to include a variety to show how automation can remove some thought when trying to return data from devices. Whether its vendor A or vendor B, at the end of the day we want the information required for the task at hand.</p>
<h2 id="script-breakdown">Script Breakdown</h2>
<p><code>All the Imports</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> fastapi <span style="color:#f92672">import</span> FastAPI
<span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_get
<span style="color:#f92672">from</span> yaml <span style="color:#f92672">import</span> safe_load
<span style="color:#f92672">import</span> urllib3
</code></pre></div><ul>
<li>FastAPI – Used to create a class instance of FastAPI.</li>
<li>InitNornir – Similar to FastAPI above, in this case Nornir will be initialized with a config file.</li>
<li>napalm_get – Heavily used to retrieve all the information in our API calls.</li>
<li>safe_load – Will be used to load hosts.yaml file (specific API call).</li>
<li>urllib3 – At the moment this will simply be disabling insecure warnings in the terminal output.</li>
</ul>
<p><code>Inits</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">urllib3<span style="color:#f92672">.</span>disable_warnings(urllib3<span style="color:#f92672">.</span>exceptions<span style="color:#f92672">.</span>InsecureRequestWarning)

nr <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;config/config.yaml&#34;</span>)

app <span style="color:#f92672">=</span> FastAPI()


<span style="color:#a6e22e">@app</span><span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;/&#34;</span>)
<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">root</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Says hi!&#34;&#34;&#34;</span>
    <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;message&#34;</span>: <span style="color:#e6db74">&#34;Hello JulioPDX&#34;</span>}
</code></pre></div><p>As mentioned previously, we will be disabling the insecure warnings for our lab. We are initializing Nornir with the nr variable and FastAPI to the app variable. Something new is the ‘@app.get(“/”)’ on line eight. From the article this is known as a decorator in python and it relates to the function definition immediately below it. You will see this pattern further in this writing. For this example, if we run the app pointing to “/” it should return “Hello JulioPDX”. Lets start the app!</p>
<p><code>Starting the App</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> <span style="color:#f92672">[</span>juliopdx@pbpro net_auto_fastapi<span style="color:#f92672">]</span>$ uvicorn play:app --reload
INFO:     Will watch <span style="color:#66d9ef">for</span> changes in these directories: <span style="color:#f92672">[</span><span style="color:#e6db74">&#39;/home/juliopdx/git/net_auto_fastapi&#39;</span><span style="color:#f92672">]</span>
INFO:     Uvicorn running on http://127.0.0.1:8000 <span style="color:#f92672">(</span>Press CTRL+C to quit<span style="color:#f92672">)</span>
INFO:     Started reloader process <span style="color:#f92672">[</span>99179<span style="color:#f92672">]</span> using watchgod
INFO:     Started server process <span style="color:#f92672">[</span>99184<span style="color:#f92672">]</span>
INFO:     Waiting <span style="color:#66d9ef">for</span> application startup.
INFO:     Application startup complete.
</code></pre></div><p>Sticking close to the learning so far, uvicorn is used as our server to run the app. The reload option will restart the service anytime a change is made to our play.py file and saved. Lets check out the path mentioned in earlier code.</p>
<p><img src="/blog/images/fastapi_hello.png" alt="Looking good!"></p>
<p>Now that I have my local environment to the point of the original article, I wanted to start with something simple to test my knowledge. Could there be a way to let users know what all devices will be loaded and available in Nornir? Check out the code below.</p>
<p><code>Get All the Things</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#a6e22e">@app</span><span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;/devices&#34;</span>)
<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_devices</span>():
    <span style="color:#e6db74">&#34;&#34;&#34;Returns a list of devices loaded from our hosts file&#34;&#34;&#34;</span>
    <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;./config/hosts.yaml&#34;</span>, encoding<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>) <span style="color:#66d9ef">as</span> file:
        devices <span style="color:#f92672">=</span> safe_load(file)
    <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;devices&#34;</span>: devices}
</code></pre></div><p>This is pretty neat, its a simple python operation to open a file and set the contents to a variable. Incorporating the output into FastAPI was incredibly easy. This will also show a user what devices they can work with at a simple URL or endpoint. Something else to note is that the credentials are not exposed to the user, only the portions within the hosts.yaml file.</p>
<p><img src="/blog/images/fastapi_devices.png" alt="Devices"></p>
<p>This next portion was the most interesting to me. NAPALM has a concept called getters. Basically these functions will go and grab different pieces of information from a device. I initially thought I would have to write a function for every getter. Luckily for me and you viewers watching at home, we can use path parameters when defining these endpoints! For example, if I wanted to interact with the ArubaCX device and the facts getter, I would need to provide two parameters to the endpoint. This allows the operator to just enter the URL with the correct device name and getter, all in one function definition!</p>
<p><code>All the Getters</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#a6e22e">@app</span><span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;/devices/</span><span style="color:#e6db74">{hostname}</span><span style="color:#e6db74">/napalm_get/</span><span style="color:#e6db74">{getter}</span><span style="color:#e6db74">&#34;</span>)
<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_config</span>(hostname: str, getter: str):
    <span style="color:#e6db74">&#34;&#34;&#34;Function used to interact with NAPALM and devices&#34;&#34;&#34;</span>
    rtr <span style="color:#f92672">=</span> nr<span style="color:#f92672">.</span>filter(name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>hostname<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
    <span style="color:#66d9ef">return</span> rtr<span style="color:#f92672">.</span>run(name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Get </span><span style="color:#e6db74">{</span>hostname<span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74">{</span>getter<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>, task<span style="color:#f92672">=</span>napalm_get, getters<span style="color:#f92672">=</span>[<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>getter<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>])
</code></pre></div><table>
<thead>
<tr>
<th style="text-align:center"></th>
<th style="text-align:center"></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><img src="/blog/images/napalm_arista_arp.png" alt="NAPALM Arista ARP"></td>
<td style="text-align:center"><img src="/blog/images/napalm_arista_facts.png" alt="NAPALM Arista Facts"></td>
</tr>
<tr>
<td style="text-align:center"><img src="/blog/images/napalm_aruba_lldp.png" alt="NAPALM Aruba LLDP"></td>
<td style="text-align:center"><img src="/blog/images/napalm_aruba_facts.png" alt="NAPALM Aruba Facts"></td>
</tr>
<tr>
<td style="text-align:center"><img src="/blog/images/napalm_cisco_interfaces.png" alt="NAPALM Cisco Interfaces"></td>
<td style="text-align:center"><img src="/blog/images/napalm_cisco_facts.png" alt="NAPALM Cisco Facts"></td>
</tr>
</tbody>
</table>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you for reading this far, really means a lot. So much of this can be improved and I’m excited to keep learning about python and FastAPI. Please check out the links below for more information.</p>
<ul>
<li><a href="https://unsplash.com/photos/gEN5Btvf2Eg">Featured Image by Michael Dziedzic</a></li>
<li><a href="https://realpython.com/fastapi-python-web-apis/">Real Python - Using FastAPI to Build Python Web APIs</a></li>
<li><a href="https://nornir.readthedocs.io/en/latest/">Nornir Documentation</a></li>
<li><a href="https://napalm.readthedocs.io/en/latest/support/#getters-support-matrix">NAPALM Getters</a></li>
<li><a href="https://github.com/JulioPDX/net_auto_fastapi">Repository for code</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>My Journey Learning About Internet Exchange Points (IXPs)</title>
            <link>https://juliopdx.com/2021/08/29/my-journey-learning-about-internet-exchange-points-ixps/</link>
            <pubDate>Sun, 29 Aug 2021 18:04:42 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/08/29/my-journey-learning-about-internet-exchange-points-ixps/</guid>
            <description>Introduction Hello all and thank you for checking out one of my blog posts. Really means a lot! Have you ever heard IXP mentioned in network engineering speak and thought, what the heck is that? I definitely have, and in my day to day I don’t usually interact with an IXP at all. So here I am a curious individual, wanting to learn the things. I figured, what the heck, now is as good a time as any to deep dive some IXP knowledge.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello all and thank you for checking out one of my blog posts. Really means a lot! Have you ever heard IXP mentioned in network engineering speak and thought, what the heck is that? I definitely have, and in my day to day I don’t usually interact with an IXP at all. So here I am a curious individual, wanting to learn the things. I figured, what the heck, now is as good a time as any to deep dive some IXP knowledge. Please note, what I breakdown in this post is just a sliver of what is done at an IXP or even at the internet service provider side.</p>
<h2 id="what-is-an-ixp">What is an IXP?</h2>
<p>In the absolute simplest terms… a managed switch. Seriously, that’s pretty much it. I’m only kidding a bit. IXPs allow internet service providers or content providers to have a general space to come together and peer with each other. Each participant will have a router at the IXP site and peer with every participant, route server, or a subset of the participants. Peering will be done using external BGP (eBGP). This can also be a combination of peering with a route server and a few participants. The cost to run an IXP is usually divided between the participants.</p>
<h2 id="why-do-we-need-them">Why do we need them?</h2>
<p>Imagine two internet providers with customers of their own in the west coast. The ISPs only peer with each other on the east coast. If customers on the west coast want to talk to each other, that traffic would have to travel from coast to coast just to end up back on the west coast. Not very efficient or economical. The ISPs could peer with each other at the west coast, but what if there’s another ISP or five? This would not scale very well and would become very costly. Below is a simple list of other IXP benefits:</p>
<ul>
<li>Save money</li>
<li>Traffic stays local</li>
<li>Performance</li>
<li>Better customer experience</li>
</ul>
<h2 id="our-topology">Our Topology</h2>
<p><img src="/blog/images/IXP.png" alt="Topology"></p>
<p>In the topology above we have a dual site IXP. Why a dual site? In cases where IXPs become very large, the requirement for a second site may arise. I also just wanted to play with a dual site topology for more learnings. Please note the IXP sites are not directly connected. Good for decreasing failure domain.</p>
<p>We have four ISPs, each ISP is advertising IPv4 and IPv6 networks. Every ISP has a three router setup. Routers ISP-X-1 and ISP-X-2 are edge nodes for the ISP and ISP-X-3 is internal to the ISP network (advertising the routes). I’ll mainly focus on ISP-A since all others ISPs have a very similar configurations, sub some IP settings. The IXP site also has a node acting as a route server/router collector. More on that in a bit!</p>
<h2 id="ixp-network-addressing">IXP Network Addressing</h2>
<table>
<thead>
<tr>
<th style="text-align:center">Site</th>
<th style="text-align:center">IXP1</th>
<th style="text-align:center">IXP2</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">IPv4 Peering LAN</td>
<td style="text-align:center">10.0.0.0/23</td>
<td style="text-align:center">10.0.2.0/23</td>
</tr>
<tr>
<td style="text-align:center">IPv6 Peering LAN</td>
<td style="text-align:center">2001:db8::/64</td>
<td style="text-align:center">2001:db8:0:1::/64</td>
</tr>
<tr>
<td style="text-align:center">ASN</td>
<td style="text-align:center">777(should be private ASN)</td>
<td style="text-align:center">777 (should be private ASN)</td>
</tr>
</tbody>
</table>
<p>I saw some examples mentioning the IXP LAN can be public addressing or private addressing. In my case I chose private addressing for IPv4 and the general documentation prefix for IPv6. The ASN at the IXP should be a private ASN. When I started this build I chose 777 because luck.</p>
<h2 id="isp-network-addressing">ISP Network Addressing</h2>
<table>
<thead>
<tr>
<th style="text-align:center">ISP</th>
<th style="text-align:center">A</th>
<th style="text-align:center">B</th>
<th style="text-align:center">C</th>
<th style="text-align:center">D</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">IPv4 Block</td>
<td style="text-align:center">1.1.1.0/24</td>
<td style="text-align:center">2.2.2.0/24</td>
<td style="text-align:center">3.3.3.0/24</td>
<td style="text-align:center">4.4.4.0/24</td>
</tr>
<tr>
<td style="text-align:center">IPv6 Block</td>
<td style="text-align:center">2001:db8:111::/48</td>
<td style="text-align:center">2001:db8:222::/48</td>
<td style="text-align:center">2001:db8:333::/48</td>
<td style="text-align:center">2001:db8:444::/48</td>
</tr>
<tr>
<td style="text-align:center">ASN</td>
<td style="text-align:center">111</td>
<td style="text-align:center">222</td>
<td style="text-align:center">333</td>
<td style="text-align:center">444</td>
</tr>
</tbody>
</table>
<p>Lets just pretend in our fun world that these fictional ISPs own these public addresses. The ISP-X-3 router at each ISP site will be advertising the networks shown above.</p>
<h2 id="isp-topology">ISP Topology</h2>
<p><img src="/blog/images/ISP-Edge.png" alt="ISP Edge"></p>
<p>Each ISP has two edge routers and one router internal to the ISP autonomous system. Each ISP is running OSPFv3 to redistribute internal routes and addresses on loopback interfaces for BGP peering. The internal router, in this case ISP-A-3, acts as a route reflector. A-1 and A-2 do not directly peer with each other using BGP. They are neighbors using OSPFv3 IPv4 and IPv6 address families.</p>
<h2 id="ospfv3-configuration">OSPFv3 Configuration</h2>
<p><code>ISP-A-3 OSPFv3 Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">interface Loopback0
 ip address 1.1.1.1 255.255.255.0
 ipv6 address 2001:DB8:111::1/48
!
interface Loopback1
 ip address 192.168.1.3 255.255.255.255
 ipv6 address FE80::3 link-local
 ipv6 address 2001:DB8:1::3/128
 ospfv3 1 ipv6 area 0
 ospfv3 1 ipv4 area 0
!
interface Ethernet0/0
 description To ISP-A-1
 ip address 192.168.0.3 255.255.255.254
 ipv6 address FE80::3 link-local
 ospfv3 1 ipv4 area 0
 ospfv3 1 ipv6 area 0
 bfd interval 300 min_rx 300 multiplier 3
!
interface Ethernet0/1
 description To ISP-A-2
 ip address 192.168.0.5 255.255.255.254
 ipv6 address FE80::3 link-local
 ospfv3 1 ipv4 area 0
 ospfv3 1 ipv6 area 0
 bfd interval 300 min_rx 300 multiplier 3
!

router ospfv3 1
 router-id 0.0.0.3
 bfd all-interfaces
 !
 address-family ipv4 unicast
  passive-interface Loopback0
  passive-interface Loopback1
 exit-address-family
 !
 address-family ipv6 unicast
  passive-interface Loopback0
  passive-interface Loopback1
 exit-address-family
!
</code></pre></div><p>IPv4 addressing is the same across all ISPs in the topology as well as the link local addressing. We enable OSPFv3 under both address families and set loopbacks to passive in OSPFv3. I enabled BFD on pretty much any link I could to speed up failure detection, especially with BGP peers.</p>
<h2 id="ibgp-configuration">iBGP Configuration</h2>
<p><code>ISP-A-3 iBGP Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 111
 bgp log-neighbor-changes
 no bgp default ipv4-unicast
 neighbor iBGP peer-group
 neighbor iBGP remote-as 111
 neighbor iBGP update-source Loopback1
 neighbor iBGP fall-over bfd
 neighbor iBGPv6 peer-group
 neighbor iBGPv6 remote-as 111
 neighbor iBGPv6 update-source Loopback1
 neighbor iBGPv6 fall-over bfd
 neighbor 2001:DB8:1::1 peer-group iBGPv6
 neighbor 2001:DB8:1::2 peer-group iBGPv6
 neighbor 192.168.1.1 peer-group iBGP
 neighbor 192.168.1.2 peer-group iBGP
 !
 address-family ipv4
  network 1.1.1.0 mask 255.255.255.0
  neighbor iBGP route-reflector-client
  neighbor 192.168.1.1 activate
  neighbor 192.168.1.2 activate
 exit-address-family
 !
 address-family ipv6
  network 2001:DB8:111::/48
  neighbor iBGPv6 route-reflector-client
  neighbor 2001:DB8:1::1 activate
  neighbor 2001:DB8:1::2 activate
 exit-address-family
</code></pre></div><p>This configuration looks a bit long but lets break it down. We disable the default BGP for IPv4 setup. We will be using both address families for neighbor relationships. There is a peer group for IPv4 neighbors and IPv6 neighbors to cut down on commands. Especially if more neighbors were involved. After that we will advertise the routes we need and set the peers in the peer groups to be route reflector clients.</p>
<p><code>ISP-A-1 iBGP Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 111
 neighbor 2001:DB8:1::3 remote-as 111
 neighbor 2001:DB8:1::3 update-source Loopback0
 neighbor 2001:DB8:1::3 fall-over bfd
 neighbor 192.168.1.3 remote-as 111
 neighbor 192.168.1.3 update-source Loopback0
 neighbor 192.168.1.3 fall-over bfd
 !
 address-family ipv4
  neighbor 192.168.1.3 activate
  neighbor 192.168.1.3 next-hop-self
  neighbor 192.168.1.3 route-map iBGP_IPv4_IN in
 exit-address-family
 !
 address-family ipv6
  neighbor 2001:DB8:1::3 activate
  neighbor 2001:DB8:1::3 next-hop-self
  neighbor 2001:DB8:1::3 route-map iBGP_IPv6_IN in
 exit-address-family
</code></pre></div><p>I’ll spare you from the configuration on ISP-A-2, it is so close to being a copy of ISP-A-1. Not much new here, just enabling the neighbor relationship with ISP-A-3. Something new is the route-map being applied inbound. One of the resources I used mentioned the ISP border routers at the IXP should, for the most part, not carry the full internet routing table. Lets say for example, ISPA wanted to advertise only networks 1.1.1.0/24 and 2001:db8:111::/48 at this IXP. I accomplished this by filtering all incoming updates from our iBGP neighbor to only accept those prefixes. See prefix lists and route maps below.</p>
<p><code>Inbound Prefix Lists and Route Maps</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!
ip prefix-list INBOUND seq 5 permit 1.1.1.0/24
!
ipv6 prefix-list INBOUND_v6 seq 5 permit 2001:DB8:111::/48
!
route-map iBGP_IPv4_IN permit 10
 match ip address prefix-list INBOUND
!
route-map iBGP_IPv6_IN permit 10
 match ipv6 address prefix-list INBOUND_v6
!
</code></pre></div><h2 id="ebgp-configuration">eBGP Configuration</h2>
<p><code>ISP-A-1 eBGP Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 111
 bgp router-id 10.0.0.21
 bgp log-neighbor-changes
 no bgp default ipv4-unicast
 neighbor IXP peer-group
 neighbor IXP fall-over bfd
 neighbor IXPv6 peer-group
 neighbor IXPv6 fall-over bfd
 neighbor 10.0.0.1 remote-as 777
 neighbor 10.0.0.1 peer-group IXP
 neighbor 10.0.0.22 remote-as 222
 neighbor 10.0.0.22 peer-group IXP
 neighbor 10.0.0.23 remote-as 333
 neighbor 10.0.0.23 peer-group IXP
 neighbor 10.0.0.24 remote-as 444
 neighbor 10.0.0.24 peer-group IXP
 neighbor 2001:DB8::1 remote-as 777
 neighbor 2001:DB8::1 peer-group IXPv6
 neighbor 2001:DB8::222:1 remote-as 222
 neighbor 2001:DB8::222:1 peer-group IXPv6
 neighbor 2001:DB8::333:1 remote-as 333
 neighbor 2001:DB8::333:1 peer-group IXPv6
 neighbor 2001:DB8::444:1 remote-as 444
 neighbor 2001:DB8::444:1 peer-group IXPv6
 !
 address-family ipv4
  network 1.1.1.0 mask 255.255.255.0
  neighbor IXP send-community both
  neighbor IXP soft-reconfiguration inbound
  neighbor IXP route-map IXP_IPv4_OUT out
  neighbor 10.0.0.1 activate
  neighbor 10.0.0.22 activate
  neighbor 10.0.0.23 activate
  neighbor 10.0.0.24 activate
 exit-address-family
 !
 address-family ipv6
  network 2001:DB8:111::/48
  neighbor IXPv6 send-community both
  neighbor IXPv6 soft-reconfiguration inbound
  neighbor IXPv6 route-map IXP_IPv6_OUT out
  neighbor 2001:DB8::1 activate
  neighbor 2001:DB8::222:1 activate
  neighbor 2001:DB8::333:1 activate
  neighbor 2001:DB8::444:1 activate
 exit-address-family
!
</code></pre></div><p>I&rsquo;ll admit the eBGP configuration is a bit more busy. Here we have peer groups for both IPv4 (IXP) and IPv6 (IXPv6). Sending community attributes for potential traffic engineering or policies. Something that I should mention for more official implementation, authentication between BGP peers would be great as well as filtering per neighbor. For example, if we were to peer with 10.0.0.22 and expect to receive the 2.2.2.0/24 network. We could add a filter to only accept that network inbound from 10.0.0.22. I created a simple filter to only filter the routes we are sending to our eBGP neighbors. Example is below.</p>
<p><code>Outbound Prefix Lists and Route Maps</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!
ip prefix-list OUTBOUND seq 5 permit 1.1.1.0/24
!
ipv6 prefix-list OUTBOUND_v6 seq 5 permit 2001:DB8:111::/48
route-map IXP_IPv4_OUT permit 10
 match ip address prefix-list OUTBOUND
!
route-map IXP_IPv6_OUT permit 10
 match ipv6 address prefix-list OUTBOUND_v6
!
</code></pre></div><h2 id="ixp-switching">IXP Switching</h2>
<p>I did not go deep in the reading for IXP hardware. My best thought would be a pretty decent switch with speeds up to 400G at this point. I’m sure there are a lot of factors that go into these decisions. Check out the IXP wish list from euro-ix linked at the end.</p>
<p>I’m going to turn our attention to the actual switch configuration. This is again only a sliver of what should be configured on the switch. Below is what I came up with in this lab scenario.</p>
<p><code>IXP Switchport Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!
interface Ethernet0/0
 description To ISP-A-1
 switchport access vlan 1234
 switchport mode access
 switchport port-security
 switchport port-security violation restrict
 switchport port-security mac-address sticky
 switchport port-security mac-address sticky aabb.cc00.0310
 duplex auto
 no cdp enable
 spanning-tree portfast
 spanning-tree bpdufilter enable
!
interface Ethernet1/0
 description To RS1
 switchport access vlan 1234
 switchport mode access
 switchport port-security
 switchport port-security violation restrict
 switchport port-security mac-address sticky
 switchport port-security mac-address sticky 5000.000b.0001
 duplex auto
 spanning-tree portfast
 ip dhcp snooping trust
!
</code></pre></div><p>I included just the ports to ISP-A-1 and RS1 for brevity and save your eyes a bit. VLAN 1234 will be used for peering. Portfast is enabled to bring up the interfaces quicker. Port security and MAC address sticky is used to prevent a rogue device from being connected and trying to join our peering network. In our case the single MAC will be remembered even on a switch reboot. I set the mode to restrict in this lab, this should allow the port to stay up and packets from rogue MAC addresses will be dropped. Alerts can then be sent to a network management system. I disabled CDP just in case some rogue device gets access, they at least wont know what network the neighbor device is on. BPDU filter is on just in case some rogue switch is connected and prevent things from going horribly wrong. All other interfaces not in use are shut down.</p>
<h2 id="ixp-services">IXP Services</h2>
<p>Apart from giving ISPs or content providers the ability to peer with each other. IXPs can offer several additional services:</p>
<ul>
<li>Country Code Top Level Domain – Ability to host country’s top level DNS</li>
<li>Root Servers – Used to reduce latency of DNS resolutions</li>
<li>Route Collector – Ability to view the routing information at the IXP</li>
<li>Looking Glass – Public or members only view of the Route Collector routing information</li>
<li>NTP – Possibility to host a stratum 1 time source</li>
</ul>
<h2 id="route-collector">Route Collector</h2>
<p>The services mentioned above are only a handful of what an IXP can provide. I’m going to focus on one in particular, the Route Collector. An IXP can have a policy for each participant that they must peer with a local route collector. The collector has a simple function, receive all the routes, and distribute none. You may have noticed two nodes in the topology named RS1 and RS2. The name may be misleading as it can stand for route server, but for our purposes just think of them as a route collector. If you are wondering, route servers can make BGP configuration a bit more simple for ISPs at an IXP since they only have to peer with the route server vs each individual participant. This has a possible negative of the ISP losing a bit of policy control with BGP.</p>
<p>Our route collectors are Cumulus Linux VMs running a neat BGP setup. I’m not entirely sure on the correct way to setup the route collector but this made sense to me. Since the route collector cannot forward any routes that are learned, we need to create a policy. The most simple thing that came to mind was to create a general deny all route map and apply it to our peer group. Check it out below. I’ll focus on RS1 as RS2 has a very very similar config. So much so that I basically ran “net show configuration commands”, copied the output, updated about two addresses, and then pasted them over to RS2.</p>
<p><code>RS1 BGP Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">router bgp 777
  no bgp default ipv4-unicast
  neighbor IXP peer-group
  neighbor IXP remote-as external
  neighbor IXP bfd 3 300 300
  neighbor IXPv6 peer-group
  neighbor IXPv6 remote-as external
  neighbor IXPv6 bfd 3 300 300
  bgp listen limit 90
  bgp listen range 10.0.0.0/23 peer-group IXP
  bgp listen range 2001:db8::/64 peer-group IXPv6

  address-family ipv4 unicast
    neighbor IXP activate
    neighbor IXP soft-reconfiguration inbound
    neighbor IXP route-map RS_DENY out
    neighbor IXP send-community both

  address-family ipv6 unicast
    neighbor IXPv6 activate
    neighbor IXPv6 soft-reconfiguration inbound
    neighbor IXPv6 route-map RS_DENY out
    neighbor IXPv6 send-community both

route-map RS_DENY deny 1
</code></pre></div><p>Okay so the config was small enough I just included the whole thing. At the bottom you can see my generic deny route map. Check out those bgp listen commands, I think its the coolest thing. In my little IXP world, I didn’t want to keep manually adding all the BGP peers to the route collector. So the route collector is listening for any peer requests that come from our peering LAN. Probably not the most secure, I would recommend reading up on the best way to set that up.</p>
<h2 id="validate">Validate</h2>
<p>Lets check some neighbors and routes!</p>
<p><code>ISP-A-3 OSPFv3 Neighbors</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">ISP-A-3#show ospfv3 neighbor

          OSPFv3 1 address-family ipv4 (router-id 0.0.0.3)

Neighbor ID     Pri   State           Dead Time   Interface ID    Interface
0.0.0.2           1   FULL/BDR        00:00:35    5               Ethernet0/1
0.0.0.1           1   FULL/BDR        00:00:37    5               Ethernet0/0

          OSPFv3 1 address-family ipv6 (router-id 0.0.0.3)

Neighbor ID     Pri   State           Dead Time   Interface ID    Interface
0.0.0.2           1   FULL/BDR        00:00:34    5               Ethernet0/1
0.0.0.1           1   FULL/BDR        00:00:33    5               Ethernet0/0
</code></pre></div><p><code>ISP-A-3 BGP Neighbors</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">ISP-A-3#show bgp ipv4 unicast summary | b Nei
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.168.1.1     4          111    1439    1429       14    0    0 21:30:04        3
192.168.1.2     4          111    1370    1373       14    0    0 20:36:57        3
ISP-A-3#show bgp ipv6 unicast summary | b Nei
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
2001:DB8:1::1   4          111    1474    1424       43    0    0 21:11:37        3
2001:DB8:1::2   4          111    1486    1440       43    0    0 21:11:36        3
ISP-A-3#
</code></pre></div><p><code>ISP-A-3 BGP Networks</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">ISP-A-3#show bgp ipv4 unicast | b Net
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  1.1.1.0/24       0.0.0.0                  0         32768 i
 * i 2.2.2.0/24       192.168.1.2              0    100      0 222 i
 *&gt;i                  192.168.1.1              0    100      0 222 i
 * i 3.3.3.0/24       192.168.1.2              0    100      0 333 i
 *&gt;i                  192.168.1.1              0    100      0 333 i
 * i 4.4.4.0/24       192.168.1.2              0    100      0 444 i
 *&gt;i                  192.168.1.1              0    100      0 444 i
ISP-A-3#show bgp ipv6 unicast | b Net
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  2001:DB8:111::/48
                       ::                       0         32768 i
 * i 2001:DB8:222::/48
                       2001:DB8:1::2            0    100      0 222 i
 *&gt;i                  2001:DB8:1::1            0    100      0 222 i
 * i 2001:DB8:333::/48
                       2001:DB8:1::2            0    100      0 333 i
 *&gt;i                  2001:DB8:1::1            0    100      0 333 i
 * i 2001:DB8:444::/48
                       2001:DB8:1::2            0    100      0 444 i
 *&gt;i                  2001:DB8:1::1            0    100      0 444 i
ISP-A-3#
</code></pre></div><p><code>RS1 BGP Neighbors</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cumulus@RS1:mgmt:~$ net show bgp summary
show bgp ipv4 unicast summary
=============================
BGP router identifier 10.0.0.1, local AS number 777 vrf-id 0
BGP table version 72
RIB entries 7, using 1344 bytes of memory
Peers 4, using 85 KiB of memory
Peer groups 2, using 128 bytes of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt
*10.0.0.21      4        111     27760     28419        0    0    0 23:41:01            1        0
*10.0.0.22      4        222     63678     65191        0    0    0 2d06h19m            1        0
*10.0.0.23      4        333     63678     65191        0    0    0 2d06h19m            1        0
*10.0.0.24      4        444     28302     28971        0    0    0 1d00h08m            1        0

Total number of neighbors 4
* - dynamic neighbor
4 dynamic neighbor(s), limit 90


show bgp ipv6 unicast summary
=============================
BGP router identifier 10.0.0.1, local AS number 777 vrf-id 0
BGP table version 49
RIB entries 7, using 1344 bytes of memory
Peers 4, using 85 KiB of memory
Peer groups 2, using 128 bytes of memory

Neighbor         V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt
*2001:db8::111:1 4        111     27760     28417        0    0    0 23:40:55            1        0
*2001:db8::222:1 4        222     63716     65191        0    0    0 2d06h19m            1        0
*2001:db8::333:1 4        333     63715     65191        0    0    0 2d06h19m            1        0
*2001:db8::444:1 4        444     63714     65191        0    0    0 2d06h19m            1        0

Total number of neighbors 4
* - dynamic neighbor
4 dynamic neighbor(s), limit 90


show bgp l2vpn evpn summary
===========================
% No BGP neighbors found
cumulus@cumulus:mgmt:~$
</code></pre></div><p><code>RS1 BGP Routes</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">cumulus@cumulus:mgmt:~$  net show bgp
show bgp ipv4 unicast
=====================
BGP table version is 72, local router ID is 10.0.0.1, vrf id 0
Default local pref 100, local AS 777
Status codes:  s suppressed, d damped, h history, * valid, &gt; best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop&#39;s vrf id, &lt; announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*&gt; 1.1.1.0/24       10.0.0.21                              0 111 i
*&gt; 2.2.2.0/24       10.0.0.22                              0 222 i
*&gt; 3.3.3.0/24       10.0.0.23                              0 333 i
*&gt; 4.4.4.0/24       10.0.0.24                              0 444 i

Displayed  4 routes and 4 total paths


show bgp ipv6 unicast
=====================
BGP table version is 49, local router ID is 10.0.0.1, vrf id 0
Default local pref 100, local AS 777
Status codes:  s suppressed, d damped, h history, * valid, &gt; best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop&#39;s vrf id, &lt; announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*&gt; 2001:db8:111::/48
                    fe80::a8bb:ccff:fe00:310
                                                           0 111 i
*&gt; 2001:db8:222::/48
                    fe80::a8bb:ccff:fe00:510
                                                           0 222 i
*&gt; 2001:db8:333::/48
                    fe80::a8bb:ccff:fe00:710
                                                           0 333 i
*&gt; 2001:db8:444::/48
                    fe80::a8bb:ccff:fe00:810
                                                           0 444 i

Displayed  4 routes and 4 total paths
cumulus@cumulus:mgmt:~$
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you again for checking out this post, I really appreciate it. Please send over any tips or just ping me if something I mentioned is way out into left field. If you are curious about IXPs, please check out the links below! I will include all configurations for the devices in the topology on my GitHub (linked below). Thank you to all the IXP operators around the world!</p>
<ul>
<li><a href="https://unsplash.com/photos/A7658fvN2cU">Featured Image by Finn Whelen</a></li>
<li><a href="https://www.pacnog.org/pacnog6/IXP/IXP-design.pdf">PACNOG – Internet Exchange Point Design</a></li>
<li><a href="https://learn.nsrc.org/bgp/ixp_history">Network Startup Resource Center – IXP Design and Implementation</a></li>
<li><a href="https://www.euro-ix.net/media/filer_public/0a/5b/0a5b4a4e-e032-41f8-b0f7-43c1375c5442/ixp-wishlist.pdf">EURO-IX – Internet Exchange Point Wishlist</a></li>
<li><a href="https://github.com/JulioPDX/IXP_Learnings">Configuration Files</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Basic Network Testing With Nornir and NTC Templates</title>
            <link>https://juliopdx.com/2021/08/15/basic-network-testing-with-nornir-and-ntc-templates/</link>
            <pubDate>Sun, 15 Aug 2021 11:12:03 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/08/15/basic-network-testing-with-nornir-and-ntc-templates/</guid>
            <description>Introduction Hello everyone and thank you for checking out another blog post. This time looking at testing the network. In my previous post, I mentioned testing could be a really neat and much needed addition to the code base. I had some spare time this weekend and couldn’t wait. Ill be honest, I consider myself a novice in python and programming in general. There is definitely many ways to accomplish a task and I’m sure there are plenty of more efficient ways to perform the actions I&amp;rsquo;ll share with you.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello everyone and thank you for checking out another blog post. This time looking at testing the network. In my previous post, I mentioned testing could be a really neat and much needed addition to the code base. I had some spare time this weekend and couldn’t wait. Ill be honest, I consider myself a novice in python and programming in general. There is definitely many ways to accomplish a task and I’m sure there are plenty of more efficient ways to perform the actions I&rsquo;ll share with you.</p>
<h2 id="the-road-to-network-testing">The Road to Network Testing</h2>
<p>Initially I wanted to incorporate pytest for this weekend project. It generally made sense and experienced folks working in python use this to test anything from simple functions to extremely large code bases. I ran into a few issues when trying to incorporate pytest. Again, I’m fairly inexperienced in this realm and I’m sure a more experienced person would not run into these issues. The main issue I had with pytest was jobs failing after the first failed assert. Even when explicitly setting the <em>–maxfail</em> option. Overall I thought working with pytest was really fun and I cant wait to dig into it more.</p>
<p>Phase two of network testing had me working with Nornir and Netmiko to send a command, set <code>use_textfsm=True</code>, and then run a few if statements to check matching configuration. I&rsquo;ll include a portion of what that looked like below.</p>
<p><code>Nornir and Netmiko</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">result <span style="color:#f92672">=</span> nornir<span style="color:#f92672">.</span>run(
    name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;GET VRF&#34;</span>,
    task<span style="color:#f92672">=</span>netmiko_send_command,
    command_string<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;show vrf&#34;</span>,
    use_textfsm<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
)

<span style="color:#66d9ef">for</span> host <span style="color:#f92672">in</span> nornir<span style="color:#f92672">.</span>inventory<span style="color:#f92672">.</span>hosts<span style="color:#f92672">.</span>keys():
    vrf_parsed <span style="color:#f92672">=</span> result[host]<span style="color:#f92672">.</span>result
    list_of_vrf_values <span style="color:#f92672">=</span> [value <span style="color:#66d9ef">for</span> elem <span style="color:#f92672">in</span> vrf_parsed <span style="color:#66d9ef">for</span> value <span style="color:#f92672">in</span> elem<span style="color:#f92672">.</span>values()]

    print()

    <span style="color:#66d9ef">for</span> value <span style="color:#f92672">in</span> main_vrfs:
        <span style="color:#66d9ef">if</span> value <span style="color:#f92672">in</span> list_of_vrf_values:
            pretty(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;[bold blue]</span><span style="color:#e6db74">{</span>value<span style="color:#e6db74">}</span><span style="color:#e6db74"> VRF is configured on </span><span style="color:#e6db74">{</span>host<span style="color:#e6db74">}</span><span style="color:#e6db74">[/bold blue]&#34;</span>)
        <span style="color:#66d9ef">else</span>:
            pretty(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;[bold red]</span><span style="color:#e6db74">{</span>value<span style="color:#e6db74">}</span><span style="color:#e6db74"> VRF is not configured on </span><span style="color:#e6db74">{</span>host<span style="color:#e6db74">}</span><span style="color:#e6db74">[/bold red]&#34;</span>)
</code></pre></div><p>This approach generally worked but I had an issue in my lab. I’m not sure if it was a Netmiko issue or just my lab being weird and getting connection timeouts. In the end I decided to use Nornir with Scrapli to send the commands. Connections when using Scrapli seemed to just work, great job Carl! Once this was retrieved, I could then use the <em>parse_output</em> function from the <em>ntc_templates</em> library. Feel free to check out the repository with the test script (linked below).</p>
<h2 id="vrf-validation">VRF Validation</h2>
<p><code>Imports</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> os
<span style="color:#f92672">import</span> yaml
<span style="color:#f92672">from</span> rich <span style="color:#f92672">import</span> print <span style="color:#66d9ef">as</span> pretty
<span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_scrapli.tasks <span style="color:#f92672">import</span> send_command
<span style="color:#f92672">from</span> ntc_templates.parse <span style="color:#f92672">import</span> parse_output
</code></pre></div><p>The imports are nothing fancy. Just importing everything we will need for the script.</p>
<ul>
<li>os – Set environemt variables during script run</li>
<li>yaml – Handle interaction with yaml stuffs</li>
<li>rich – Because rich, thanks Will</li>
<li>InitNornir- Run all the things for Nornir</li>
<li>send_command – To send commands 🙂</li>
<li>parse_output – Great library for turning output into structured data</li>
</ul>
<p><code>load yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">main_vrfs <span style="color:#f92672">=</span> []

<span style="color:#75715e"># Create list of VRFs defined in groups.yaml</span>
<span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;groups.yaml&#34;</span>) <span style="color:#66d9ef">as</span> f:
    data <span style="color:#f92672">=</span> yaml<span style="color:#f92672">.</span>load(f, Loader<span style="color:#f92672">=</span>yaml<span style="color:#f92672">.</span>FullLoader)[<span style="color:#e6db74">&#34;pe&#34;</span>][<span style="color:#e6db74">&#34;data&#34;</span>][<span style="color:#e6db74">&#34;vrfs&#34;</span>]
    <span style="color:#66d9ef">for</span> vrf <span style="color:#f92672">in</span> data:
        main_vrfs<span style="color:#f92672">.</span>append(vrf[<span style="color:#e6db74">&#34;name&#34;</span>])
</code></pre></div><p>I thought this was pretty slick. We are opening the <em>groups.yaml</em> file seen from the previous post. This file lists all of our VRFs to be configured on the PE devices. Why not reuse it and save some typing? Once that is done we set the VRF portion of the yaml file to a variable called <em>data</em>. After this we loop over all the VRFs and add just the name to our empty list above. Hold that thought as this list will be used later in the script.</p>
<p><code>test_vrf function intro</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">test_vrf</span>():

    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Main VRF test
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
    os<span style="color:#f92672">.</span>environ[
        <span style="color:#e6db74">&#34;NET_TEXTFSM&#34;</span>
    ] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;./venv/lib/python3.9/site-packages/ntc_templates/templates&#34;</span>

    nornir <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;config.yaml&#34;</span>)

    result <span style="color:#f92672">=</span> nornir<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;GET VRF&#34;</span>,
        task<span style="color:#f92672">=</span>send_command,
        command<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;show vrf&#34;</span>,
    )
</code></pre></div><p>If any of you have worked with pytest, you can tell by the function naming that I was serious about incorporating it. I will in the future! Most of this has been shown in previous posts but one little difference is that <em>os.environ section</em>. When I was building this, the script couldn’t locate the <em>ntc_templates</em>. Essentially, we are just telling the script where to find the templates. You will probably have to update this for your environment.</p>
<p><code>For all the hosts</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">    <span style="color:#66d9ef">for</span> host <span style="color:#f92672">in</span> nornir<span style="color:#f92672">.</span>inventory<span style="color:#f92672">.</span>hosts<span style="color:#f92672">.</span>keys():
        remote_vrfs <span style="color:#f92672">=</span> []
        vrf_parsed <span style="color:#f92672">=</span> parse_output(
            platform<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;cisco_ios&#34;</span>, command<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;show vrf&#34;</span>, data<span style="color:#f92672">=</span>result[host]<span style="color:#f92672">.</span>result
        )

        <span style="color:#75715e"># Looping over all parsed VRFs to build new list</span>
        <span style="color:#66d9ef">for</span> remote_vrf <span style="color:#f92672">in</span> vrf_parsed:
            remote_vrfs<span style="color:#f92672">.</span>append(remote_vrf[<span style="color:#e6db74">&#34;name&#34;</span>])
</code></pre></div><p>I really love this about python and programming in general, what you see above is fairly basic and learned pretty early on in programming studies. But the power it can demonstrate is really neat to me. At this point in the script, all hosts have been connected to and information captured. We are using this initial for loop to loop over the data that was gathered from the <em>show vrf</em> command. We create an empty list called <em>remote_vrfs</em>, then run the <em>parse_output</em> function from the <em>ntc_templates</em> library to get data in a structured format. Once that is done we run another for loop to add VRF names gathered from PE routers to our previously empty list. At this point we now have two lists created. One for VRFs we want configured on devices and one from the actual VRFs already configured on the PE routers.</p>
<p><code>Set Theory</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">        set_diff <span style="color:#f92672">=</span> set(main_vrfs) <span style="color:#f92672">-</span> set(remote_vrfs)

        <span style="color:#75715e"># If a set difference is found, print the nice red output!</span>
        <span style="color:#66d9ef">if</span> set_diff:
            pretty(
                <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;[bold red]The following VRFs are not configured on </span><span style="color:#e6db74">{</span>host<span style="color:#e6db74">}</span><span style="color:#e6db74">: </span><span style="color:#e6db74">{</span>set_diff [<span style="color:#f92672">/</span>bold red]<span style="color:#e6db74">&#34;</span>
            )
        <span style="color:#66d9ef">else</span>:
            pretty(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;[bold blue]All VRF tests passed on router </span><span style="color:#e6db74">{</span>host<span style="color:#e6db74">}</span><span style="color:#e6db74">[/bold blue]&#34;</span>)
</code></pre></div><p>Good old set theory to the rescue. I learned this a while back from a course by… who other than Nick Russo. Discrete mathematics in high school may have helped but that was way too long ago. We take the two lists we have already created, then convert them to sets. From there we just look for the difference between <em>main_vrfs</em> set and the <em>remote_vrfs</em> set. Below is an example on the interpreter of what I hope to accomplish.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">&gt;&gt;&gt;</span> main_vrfs <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;CUSTOMER_777&#34;</span>,<span style="color:#e6db74">&#34;CUSTOMER_789&#34;</span>}
<span style="color:#f92672">&gt;&gt;&gt;</span> remote_vrfs <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;CUSTOMER_777&#34;</span>,<span style="color:#e6db74">&#34;CUSTOMER_789&#34;</span>,<span style="color:#e6db74">&#34;MGMT&#34;</span>}
<span style="color:#f92672">&gt;&gt;&gt;</span> main_vrfs <span style="color:#f92672">-</span> remote_vrfs
set()
<span style="color:#f92672">&gt;&gt;&gt;</span>
</code></pre></div><h2 id="script-output">Script Output</h2>
<p><img src="/blog/images/vrf_check_working.png" alt="VRF Test Passed"></p>
<p><img src="/blog/images/vrf_check_not_working.png" alt="VRF Test Failed"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you for staying this long. I hope you enjoyed the post and found something useful. As you can see I have a long way to go with network testing but it really is an important skill. I&rsquo;ll include some links below!</p>
<ul>
<li><a href="https://unsplash.com/photos/oMpAz-DN-9I">Featured Image by Greg Rakozy</a></li>
<li><a href="https://unsplash.com/photos/DX9X0g0Cg88">Featured Image by Ildefonso Polo</a></li>
<li><a href="https://github.com/JulioPDX/auto_mpls_l3vpn">GitHub Repository</a></li>
<li><a href="https://nornir.readthedocs.io/en/latest/">Nornir</a></li>
<li><a href="https://github.com/scrapli/nornir_scrapli">Scrapli</a></li>
<li><a href="https://github.com/networktocode/ntc-templates">NTC Templates</a></li>
<li><a href="https://www.geeksforgeeks.org/python-set-difference/">Set Theory</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Automating MPLS L3VPNs With Nornir</title>
            <link>https://juliopdx.com/2021/08/14/automating-mpls-l3vpns-with-nornir/</link>
            <pubDate>Sat, 14 Aug 2021 10:38:25 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/08/14/automating-mpls-l3vpns-with-nornir/</guid>
            <description>Introduction Hello all and thank you for checking out another one of my blog posts. It really means a lot. I recently completed the Cisco ENARSI exam after a few months of studying. It was honestly pretty difficult for me but the pass was worth it! I may have to write a blog post on studying for and passing that exam. In this post I will break down how I automated some parts of an MPLS L3VPN deployment.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/auto_mpls.png" alt="Topology"></p>
<h2 id="introduction">Introduction</h2>
<p>Hello all and thank you for checking out another one of my blog posts. It really means a lot. I recently completed the Cisco ENARSI exam after a few months of studying. It was honestly pretty difficult for me but the pass was worth it! I may have to write a blog post on studying for and passing that exam. In this post I will break down how I automated some parts of an MPLS L3VPN deployment. Configuring the provider edge devices to allow connectivity between customer edge routers.</p>
<p>In serious news, that was the intro you see in those recipe websites where you just want to see the ingredients and the cooking directions but there’s a whole life story on the dish or how it came about…. well this blog post is similar! Usually when studying for an exam I tend to get tunnel vision and focus on the task at hand. That also had me dropping the love for automation a bit. One of the main points in ENARSI is learning about MPLS and more specifically MPLS L3VPNs. I had such a joy learning more about this topic and running through multiple labs to get used to the technology. I wont go too deep into MPLS in this post since I’m assuming the reader is trying to automate that exact task or is familiar with it already.</p>
<h2 id="topology-brief">Topology Brief</h2>
<p>As you can see by the featured image in this post, we have four provider edge (PE) routers and four customer edge (CE) routes. Each PE device connects to one CE device, and all PE routers connect to the provider (P) router, also acting as our BGP route reflector. You might be thinking, why the route reflector? You could run iBGP sessions between all PE devices but down the road that can lead to a lot of overhead. In this case all PE devices only have one internal BGP relationship between the P router and one external BGP peering with the CE routers. This also makes the iBGP configuration on the PE routers very cookie cutter. In this case we are pretending not to have control of the CE devices, so those will already be preconfigured. I will include the configurations on the nodes so you can see what is all preconfigured.</p>
<h2 id="ce-routers-configuration-example">Ce Routers Configuration Example</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">interface Loopback0
 ip address 172.16.1.1 255.255.255.0
!
interface Ethernet0/0
 ip address 10.0.11.2 255.255.255.0
!

router bgp <span style="color:#ae81ff">789</span>
 bgp log-neighbor-changes
 network 172.16.1.0 mask 255.255.255.0
 neighbor 10.0.11.1 remote-as <span style="color:#ae81ff">12345</span>
 neighbor 10.0.11.1 allowas-in
</code></pre></div><p>Every CE router looks very similar, one lookback interface to be advertised to the PE router and a small bit of BGP configuration. You could use different autonomous system (AS) numbers between customer routers, in this case I used the same AS for the same customer. In BGP this would introduce a loop since the router will see its own AS in the path. This design option adds the command of <em>allowas-in</em> to our BGP configuration. This feature allows a route to be received even if the routers own AS is in path.</p>
<h2 id="pe-routers-configuration-example">PE Routers Configuration Example</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">interface Ethernet0/1
 ip address 10.0.15.1 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!

router ospf 1
 router-id 1.1.1.1
 network 1.1.1.1 0.0.0.0 area 0
 network 10.0.15.1 0.0.0.0 area 0
!
router bgp 12345
 bgp log-neighbor-changes
 no bgp default ipv4-unicast
 neighbor 5.5.5.5 remote-as 12345
 neighbor 5.5.5.5 update-source Loopback0
 !
 address-family ipv4
 exit-address-family
 !
 address-family vpnv4
  neighbor 5.5.5.5 activate
  neighbor 5.5.5.5 send-community extended
 exit-address-family
 !
!
</code></pre></div><p>Internally we are running OSPF to give MPLS all the paths to work with. Every PE device has essentially the same iBGP configuration towards the P router. If you look closely we are not even bringing up the peering relationship with the IPv4 address family. Only the VPNv4 address family will be used. This is due to the BGP peering only being used to distribute the VPNv4 routes across the PE routers.</p>
<h2 id="prr-router-configuration-example">P/RR Router Configuration Example</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">interface Loopback0
 ip address 5.5.5.5 255.255.255.255
!
interface Ethernet0/0
 ip address 10.0.15.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
interface Ethernet0/1
 ip address 10.0.25.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
interface Ethernet0/2
 ip address 10.0.35.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
interface Ethernet0/3
 ip address 10.0.45.5 255.255.255.0
 ip ospf network point-to-point
 mpls ip
!
router ospf 1
 router-id 5.5.5.5
 network 0.0.0.0 255.255.255.255 area 0
!
router bgp 12345
 bgp log-neighbor-changes
 no bgp default ipv4-unicast
 neighbor iBGP peer-group
 neighbor iBGP remote-as 12345
 neighbor iBGP update-source Loopback0
 neighbor 1.1.1.1 peer-group iBGP
 neighbor 2.2.2.2 peer-group iBGP
 neighbor 3.3.3.3 peer-group iBGP
 neighbor 4.4.4.4 peer-group iBGP
 !
 address-family ipv4
 exit-address-family
 !
 address-family vpnv4
  neighbor iBGP send-community extended
  neighbor iBGP route-reflector-client
  neighbor 1.1.1.1 activate
  neighbor 2.2.2.2 activate
  neighbor 3.3.3.3 activate
  neighbor 4.4.4.4 activate
 exit-address-family
!
</code></pre></div><p>The P router configuration does look a bit more involved but in reality its not too bad. In the case of the P router, I just advertised all the things in OSPF for simplicity. As I mentioned earlier with the PE routers, only the VPNv4 peering will be used. I created a peer group to bring down the number of commands required to make this all work.</p>
<h2 id="introduction-to-nornir">Introduction to Nornir</h2>
<p>I will break down the structure of the repository I made but I wont go too deep into details. Mainly because there’s a lot of great resources out there to check out (will link at the bottom of the post), and I want to try and keep this post some what short. Nornir is a network automation framework that is written in Python. This allows developers and engineers to build automation and tools with a very powerful programming language. This also allows individuals to easily extend the functionality of the framework to meet their needs.</p>
<p><code>config.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">inventory</span>:
  <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">SimpleInventory</span>
  <span style="color:#f92672">options</span>:
    <span style="color:#f92672">host_file</span>: <span style="color:#e6db74">&#34;hosts.yaml&#34;</span>
    <span style="color:#f92672">group_file</span>: <span style="color:#e6db74">&#34;groups.yaml&#34;</span>
<span style="color:#f92672">runner</span>:
  <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">threaded</span>
  <span style="color:#f92672">options</span>:
    <span style="color:#f92672">num_workers</span>: <span style="color:#ae81ff">10</span>
</code></pre></div><p>The <code>config.yaml</code> file is pretty important in Nornir. In the sample above we are using the <code>SimpleInventory</code> plugin and specifying where the host and group files are located. I am using 10 workers but my lab is fairly small so I could have used less here.</p>
<p><code>hosts.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">PE1</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#e6db74">&#34;192.168.10.176&#34;</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">pe</span>
  <span style="color:#f92672">data</span>:
    <span style="color:#f92672">interfaces</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Ethernet0/0</span>
        <span style="color:#f92672">vrf</span>: <span style="color:#ae81ff">CUSTOMER_789</span>
        <span style="color:#f92672">ip</span>: <span style="color:#ae81ff">10.0.11.1</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">bgp</span>:
      <span style="color:#f92672">neighbors</span>:
        - <span style="color:#f92672">vrf_name</span>: <span style="color:#ae81ff">CUSTOMER_789</span>
          <span style="color:#f92672">remote_ip</span>: <span style="color:#ae81ff">10.0.11.2</span>
          <span style="color:#f92672">remote_as</span>: <span style="color:#ae81ff">789</span>
</code></pre></div><p>I included the setup for our PE1 router, the rest follow a similar structure. Anything host specific I set at the <code>host.yaml</code> file and anything group specific is set at the <code>groups.yaml</code> file you will see shortly. I assign all PE routers to the <code>pe</code> group and set certain interface parameters as well as BGP information that will be used in the future.</p>
<p><code>groups.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">pe</span>:
  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">ios</span>
  <span style="color:#f92672">username</span>: <span style="color:#ae81ff">cisco</span>
  <span style="color:#f92672">password</span>: <span style="color:#ae81ff">cisco</span>
  <span style="color:#f92672">data</span>:
    <span style="color:#f92672">vrfs</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">CUSTOMER_789</span>
        <span style="color:#f92672">rd</span>: <span style="color:#e6db74">&#34;789:1&#34;</span>
        <span style="color:#f92672">rt_import</span>: <span style="color:#e6db74">&#34;789:1&#34;</span>
        <span style="color:#f92672">rt_export</span>: <span style="color:#e6db74">&#34;789:1&#34;</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">CUSTOMER_777</span>
        <span style="color:#f92672">rd</span>: <span style="color:#e6db74">&#34;777:1&#34;</span>
        <span style="color:#f92672">rt_import</span>: <span style="color:#e6db74">&#34;777:1&#34;</span>
        <span style="color:#f92672">rt_export</span>: <span style="color:#e6db74">&#34;777:1&#34;</span>
</code></pre></div><p>The groups file is pretty neat, since all of my routers are part of the pe group and have a similar platform as well as authentication parameters, I decided to include all of that information under the group. In this automation example I wanted all VRFs to be included on all PE routers. Therefore I also added the VRF information under the group file vs under the host data.</p>
<h2 id="breaking-down-script">Breaking Down Script</h2>
<p><code>l3vpn_deploy.py snippet</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_scrapli.tasks <span style="color:#f92672">import</span> send_configs
<span style="color:#f92672">from</span> nornir_jinja2.plugins.tasks <span style="color:#f92672">import</span> template_file
<span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_get
<span style="color:#f92672">from</span> nornir_utils.plugins.tasks.files <span style="color:#f92672">import</span> write_file
<span style="color:#f92672">from</span> nornir_utils.plugins.functions <span style="color:#f92672">import</span> print_result
<span style="color:#f92672">from</span> net_utils <span style="color:#f92672">import</span> address, mask
</code></pre></div><p>The portion above is essentially importing any tasks or functions we will need to run Nornir. Whether its importing <em>InitNornir</em> or plugins used to connect to devices and send commands. To learn more about plugins that can be used with Nornir please check out the <a href="https://nornir.tech/nornir/plugins/">nornir.tech</a> site.</p>
<p><code>l3vpn_deploy.py snippet</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():

    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Main that calls l3vpn function
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    nornir <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;config.yaml&#34;</span>)
    print(<span style="color:#e6db74">&#34;Nornir initialized with the following hosts:</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
    <span style="color:#66d9ef">for</span> host <span style="color:#f92672">in</span> nornir<span style="color:#f92672">.</span>inventory<span style="color:#f92672">.</span>hosts<span style="color:#f92672">.</span>keys():
        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>host<span style="color:#e6db74">}</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)

    result <span style="color:#f92672">=</span> nornir<span style="color:#f92672">.</span>run(task<span style="color:#f92672">=</span>l3vpn)

    print_result(result)
</code></pre></div><p>This portion of the script is our <code>main</code> function and essentially kicks off all the other portions of our script to execute. Nornir is initialized with our <code>config.yaml</code> file that includes information about our hosts, groups, and connection parameters. There is a simple print statement at the bottom to let the operator know what inventory was just initialized for this job run. We will call on the <code>l3vpn</code> function that is defined towards the top of the script that I will walk through in a bit.</p>
<h2 id="creating-the-l3vpns">Creating the L3VPNs</h2>
<p>We will need the following items on our PE routers to complete our L3VPN build:</p>
<ul>
<li>Customer VRFs created with RD and RTs</li>
<li>Assign VRF to interface facing customer router</li>
<li>Configure BGP relationship between PE and CE device under new VRF</li>
</ul>
<p>Now that we have the main portions required for our build, it was just a matter of converting the manual configurations into simple Jinja templates. I will spare you from reading about every task since they all follow the same pattern; create commands from Jinja template, split commands, send commands to PE routers. Here is a snippet below of what I mean.</p>
<p><code>l3vpn_deploy.py snippet</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">l3vpn</span>(task):

    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Main function built for L3VPN deployment tasks
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
    task1_result <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">: Creating VRFs Configuration&#34;</span>,
        task<span style="color:#f92672">=</span>template_file,
        template<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;vrf.j2&#34;</span>,
        path<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;templates/&#34;</span>,
        data<span style="color:#f92672">=</span>task<span style="color:#f92672">.</span>host[<span style="color:#e6db74">&#34;vrfs&#34;</span>],
    )
    vrf_config <span style="color:#f92672">=</span> task1_result[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>result

    task2_result <span style="color:#f92672">=</span> task<span style="color:#f92672">.</span>run(
        name<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>task<span style="color:#f92672">.</span>host<span style="color:#f92672">.</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">: Configuring VRFs on PE Nodes&#34;</span>,
        task<span style="color:#f92672">=</span>send_configs,
        configs<span style="color:#f92672">=</span>vrf_config<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>),
    )
</code></pre></div><p>I name the tasks for clarity when seeing the job output. Initially we run a <em>template_file</em> task that will create commands from a template. Then its a matter of pointing it to our specific template and feeding it data. Once that is done I set the output of that task to a variable called <em>vrf_config</em>, this can then be fed into the next task to push the commands to our PE routers.</p>
<pre tabindex="0"><code class="language-jinja2" data-lang="jinja2">{% for vrf in data %}
vrf definition {{ vrf.name }}
 rd {{ vrf.rd }}
 route-target export {{ vrf.rt_export }}
 route-target import {{ vrf.rt_import }}
 !
 address-family ipv4
 exit-address-family
{% endfor %}
</code></pre><p>Example of the VRF Jinja template above. The rest of the items for L3VPNs use the same structure. Feel free to check out the git repository linked below to see the rest. Ill show the output from a few PE and CE routers before and after the changes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">PE1#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             &lt;not set&gt;             ipv4        Et0/3
PE1#
PE2#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             &lt;not set&gt;             ipv4
PE2#
PE3#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             &lt;not set&gt;             ipv4
PE3#
PE4#show vrf brief
  Name                             Default RD            Protocols   Interfaces
  MGMT                             &lt;not set&gt;             ipv4        Et0/3
PE4#
##### CE Devices #####
CE1#show ip bgp  summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.11.1       4        12345       0       0        1    0    0 00:10:51 Active
CE1#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  172.16.1.0/24    0.0.0.0                  0         32768 i
CE1#
CE2#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.22.1       4        12345       0       0        1    0    0 00:13:22 Idle
CE2#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  172.16.2.0/24    0.0.0.0                  0         32768 i
CE2#
CE3#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.33.1       4        12345       0       0        1    0    0 never    Active
CE3#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  172.16.1.0/24    0.0.0.0                  0         32768 i
CE3#
CE4#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.44.1       4        12345       0       0        1    0    0 never    Active
CE4#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  172.16.2.0/24    0.0.0.0                  0         32768 i
CE4#
</code></pre></div><h2 id="script-output">Script Output</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">(venv) [juliopdx<span style="color:#a6e22e">@pbpro</span> auto_mpls_l3vpn]<span style="color:#960050;background-color:#1e0010">$</span> time python l3vpn_deploy<span style="color:#f92672">.</span>py
Nornir initialized <span style="color:#66d9ef">with</span> the following hosts:

PE1

PE2

PE3

PE4

l3vpn<span style="color:#f92672">***************************************************************************</span>
<span style="color:#f92672">*</span> PE1 <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">********************************************************</span>
vvvv l3vpn <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> PE1: Creating VRFs Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE1: Configuring VRFs on PE Nodes <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">----</span> PE1: Create VRF to Interfaces Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_789
 ip address <span style="color:#ae81ff">10.0.11.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>

<span style="color:#f92672">----</span> PE1: Configuring VRFs on Interfaces <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_789
 ip address <span style="color:#ae81ff">10.0.11.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>


<span style="color:#f92672">----</span> PE1: Create BGP Neighbor Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_789
  neighbor <span style="color:#ae81ff">10.0.11.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">789</span>
  neighbor <span style="color:#ae81ff">10.0.11.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE1: Configuring BGP Neighbors under VRFs <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_789
  neighbor <span style="color:#ae81ff">10.0.11.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">789</span>
  neighbor <span style="color:#ae81ff">10.0.11.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">^^^^</span> END l3vpn <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> PE2 <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">********************************************************</span>
vvvv l3vpn <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> PE2: Creating VRFs Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE2: Configuring VRFs on PE Nodes <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">----</span> PE2: Create VRF to Interfaces Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_777
 ip address <span style="color:#ae81ff">10.0.22.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>

<span style="color:#f92672">----</span> PE2: Configuring VRFs on Interfaces <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_777
 ip address <span style="color:#ae81ff">10.0.22.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>


<span style="color:#f92672">----</span> PE2: Create BGP Neighbor Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_777
  neighbor <span style="color:#ae81ff">10.0.22.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">777</span>
  neighbor <span style="color:#ae81ff">10.0.22.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE2: Configuring BGP Neighbors under VRFs <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_777
  neighbor <span style="color:#ae81ff">10.0.22.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">777</span>
  neighbor <span style="color:#ae81ff">10.0.22.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">^^^^</span> END l3vpn <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> PE3 <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">********************************************************</span>
vvvv l3vpn <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> PE3: Creating VRFs Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE3: Configuring VRFs on PE Nodes <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">----</span> PE3: Create VRF to Interfaces Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_777
 ip address <span style="color:#ae81ff">10.0.33.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>

<span style="color:#f92672">----</span> PE3: Configuring VRFs on Interfaces <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_777
 ip address <span style="color:#ae81ff">10.0.33.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>


<span style="color:#f92672">----</span> PE3: Create BGP Neighbor Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_777
  neighbor <span style="color:#ae81ff">10.0.33.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">777</span>
  neighbor <span style="color:#ae81ff">10.0.33.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE3: Configuring BGP Neighbors under VRFs <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_777
  neighbor <span style="color:#ae81ff">10.0.33.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">777</span>
  neighbor <span style="color:#ae81ff">10.0.33.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">^^^^</span> END l3vpn <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
<span style="color:#f92672">*</span> PE4 <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">********************************************************</span>
vvvv l3vpn <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
<span style="color:#f92672">----</span> PE4: Creating VRFs Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE4: Configuring VRFs on PE Nodes <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">-----------------------</span> INFO
vrf definition CUSTOMER_789
 rd <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">789</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family
vrf definition CUSTOMER_777
 rd <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target export <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 route<span style="color:#f92672">-</span>target <span style="color:#f92672">import</span> <span style="color:#ae81ff">777</span>:<span style="color:#ae81ff">1</span>
 <span style="color:#960050;background-color:#1e0010">!</span>
 address<span style="color:#f92672">-</span>family ipv4
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">----</span> PE4: Create VRF to Interfaces Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_789
 ip address <span style="color:#ae81ff">10.0.44.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>

<span style="color:#f92672">----</span> PE4: Configuring VRFs on Interfaces <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------------</span> INFO
interface Ethernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 vrf forwarding CUSTOMER_789
 ip address <span style="color:#ae81ff">10.0.44.1</span> <span style="color:#ae81ff">255.255.255.0</span>
<span style="color:#960050;background-color:#1e0010">!</span>


<span style="color:#f92672">----</span> PE4: Create BGP Neighbor Configuration <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">False</span> <span style="color:#f92672">-----------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_789
  neighbor <span style="color:#ae81ff">10.0.44.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">789</span>
  neighbor <span style="color:#ae81ff">10.0.44.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family

<span style="color:#f92672">----</span> PE4: Configuring BGP Neighbors under VRFs <span style="color:#f92672">**</span> changed : <span style="color:#66d9ef">True</span> <span style="color:#f92672">---------------</span> INFO
router bgp <span style="color:#ae81ff">12345</span>
  address<span style="color:#f92672">-</span>family ipv4 vrf CUSTOMER_789
  neighbor <span style="color:#ae81ff">10.0.44.2</span> remote<span style="color:#f92672">-</span><span style="color:#66d9ef">as</span> <span style="color:#ae81ff">789</span>
  neighbor <span style="color:#ae81ff">10.0.44.2</span> activate
 exit<span style="color:#f92672">-</span>address<span style="color:#f92672">-</span>family


<span style="color:#f92672">^^^^</span> END l3vpn <span style="color:#f92672">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>

real    <span style="color:#ae81ff">0</span>m9<span style="color:#ae81ff">.216</span>s
user    <span style="color:#ae81ff">0</span>m6<span style="color:#ae81ff">.683</span>s
sys     <span style="color:#ae81ff">0</span>m1<span style="color:#ae81ff">.079</span>s
(venv) [juliopdx<span style="color:#a6e22e">@pbpro</span> auto_mpls_l3vpn]<span style="color:#960050;background-color:#1e0010">$</span>
</code></pre></div><h2 id="vrf-creation-validation">VRF Creation Validation</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">PE1#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1
  CUSTOMER_789                     789:1                 Et0/0
  MGMT                             &lt;not set&gt;             Et0/3
PE1#
PE2#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1                 Et0/0
  CUSTOMER_789                     789:1
  MGMT                             &lt;not set&gt;
PE2#
PE3#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1                 Et0/0
  CUSTOMER_789                     789:1
  MGMT                             &lt;not set&gt;
PE3#
PE4#show ip vrf
  Name                             Default RD            Interfaces
  CUSTOMER_777                     777:1
  CUSTOMER_789                     789:1                 Et0/0
  MGMT                             &lt;not set&gt;             Et0/3
PE4#
</code></pre></div><h2 id="bgp-peers-and-routes-on-ce-routers">BGP Peers and Routes on CE Routers</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">CE1#show ip bgp  summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.11.1       4        12345      19      20        3    0    0 00:13:17        1
CE1#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  172.16.1.0/24    0.0.0.0                  0         32768 i
 *&gt;  172.16.2.0/24    10.0.11.1                              0 12345 789 i
CE1#traceroute 172.16.2.1 source 172.16.1.1 probe 1 numeric
Type escape sequence to abort.
Tracing the route to 172.16.2.1
VRF info: (vrf in name/id, vrf out name/id)
  1 10.0.11.1 0 msec
  2 10.0.15.5 [MPLS: Labels 300/24 Exp 0] 2 msec
  3 10.0.44.1 [MPLS: Label 24 Exp 0] 2 msec
  4 10.0.44.2 1 msec
CE1#
CE3#show ip bgp summary | b Neigh
Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.33.1       4        12345      25      23        3    0    0 00:17:34        1
CE3#show ip bgp | b Network
     Network          Next Hop            Metric LocPrf Weight Path
 *&gt;  172.16.1.0/24    0.0.0.0                  0         32768 i
 *&gt;  172.16.2.0/24    10.0.33.1                              0 12345 777 i
CE3#traceroute 172.16.2.1 source 172.16.1.1 probe 1 numeric
Type escape sequence to abort.
Tracing the route to 172.16.2.1
VRF info: (vrf in name/id, vrf out name/id)
  1 10.0.33.1 1 msec
  2 10.0.35.5 [MPLS: Labels 302/407 Exp 0] 2 msec
  3 10.0.22.1 [MPLS: Label 407 Exp 0] 2 msec
  4 10.0.22.2 2 msec
CE3#
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>I think there’s always room for improvement. Even off the top of my head a few additions could be the following:</p>
<ul>
<li>Automated testing</li>
<li>Validation with something like pyATS to validate VRF creation on PE routers</li>
</ul>
<p>I hope you found this a bit useful and maybe inspires you to build something you can use in everyday workload. Feel free to check out the links below to a few resources I found very helpful. Thank you again for reading this far, really means a lot.</p>
<ul>
<li><a href="https://github.com/JulioPDX/auto_mpls_l3vpn">GitHub Repository</a></li>
<li><a href="https://nornir.readthedocs.io/en/latest/">Nornir Documentation</a></li>
<li><a href="https://nornir.tech/nornir/plugins/">Nornir Plugins</a></li>
<li><a href="https://www.pluralsight.com/courses/automating-networks-python">Automating Networks with Python by Nick Russo</a></li>
<li><a href="https://saidvandeklundert.net/2020-12-06-nornir/">Nornir Blog Post by Said van de Klundert</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Python Slack Bot for Network Engineers</title>
            <link>https://juliopdx.com/2021/05/24/python-slack-bot-for-network-engineers/</link>
            <pubDate>Mon, 24 May 2021 10:19:32 -0800</pubDate>
            
            <guid>https://juliopdx.com/2021/05/24/python-slack-bot-for-network-engineers/</guid>
            <description>Introduction Hello and thank you for joining me in another blog post. I’ve wanted to mess with getting a bot running on Slack for a while now. After coming across a fantastic post by Mason Egger at Digital Ocean (linked at the end), I figured now is as good a time as any. Think of that post as a prerequisite to get you started before following along with this one.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello and thank you for joining me in another blog post. I’ve wanted to mess with getting a bot running on Slack for a while now. After coming across a fantastic post by Mason Egger at Digital Ocean (linked at the end), I figured now is as good a time as any. Think of that post as a prerequisite to get you started before following along with this one.</p>
<h2 id="caveats">Caveats</h2>
<p>A few caveats I should include. I’m a fairly novice python user, expect to see areas where code can be refactored and even total rewrites that would make the organization better. What I will demonstrate in this post is what made sense to me at this point in time. I hope you enjoy and maybe get inspired to build something you can use to assist you in your daily workflow.</p>
<h2 id="network-engineering-bot">Network Engineering Bot</h2>
<p>Below are a few of the goals I had when creating this bot.</p>
<ul>
<li>Must be multi-vendor</li>
<li>Ability to call different (non static) devices</li>
<li>Bot should react to user messages</li>
</ul>
<p>Lets walk through some code and I hope to answer how those goals were met. I’ll try not to repeat information that was already shared in Masons post. The initial code from Masons example has you create a class called CoinBot. It only made sense to me to create a separate class to define our network engineering bot. In this case we will call it NetBot, but you can name your bot whatever you like.</p>
<p><code>get_network.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> json
<span style="color:#f92672">from</span> napalm <span style="color:#f92672">import</span> get_network_driver
<span style="color:#f92672">from</span> yaml <span style="color:#f92672">import</span> safe_load
<span style="color:#f92672">import</span> urllib3

<span style="color:#75715e"># Disable warnings</span>
urllib3<span style="color:#f92672">.</span>disable_warnings(urllib3<span style="color:#f92672">.</span>exceptions<span style="color:#f92672">.</span>InsecureRequestWarning)

<span style="color:#66d9ef">class</span> <span style="color:#a6e22e">NetBot</span>:
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Defining a class called NetBot
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    USERNAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;admin&#34;</span>
    PASSWORD <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;admin&#34;</span>
    NET_BLOCK <span style="color:#f92672">=</span> {
        <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;section&#34;</span>,
        <span style="color:#e6db74">&#34;text&#34;</span>: {
            <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;mrkdwn&#34;</span>,
            <span style="color:#e6db74">&#34;text&#34;</span>: (
                <span style="color:#e6db74">&#34;Getting interface information for device :slightly_smiling_face:&#34;</span>
            ),
        },
    }

    <span style="color:#66d9ef">def</span> __init__(self, channel, device_name):
        <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">        constructor for class
</span><span style="color:#e6db74">        &#34;&#34;&#34;</span>
        self<span style="color:#f92672">.</span>channel <span style="color:#f92672">=</span> channel
        self<span style="color:#f92672">.</span>device_name <span style="color:#f92672">=</span> device_name
</code></pre></div><p>A few things to note. The variables you see in all caps are called class attributes/constants. Essentially things in the program that wont change. I kept it simple and created some credentials for authentication as class attributes. In reality this would be an environment variable or pulled from a secure source. The NET_BLOCK class attribute is just there so the bot can respond with some generic message to the user.</p>
<p>One of the first things that came to mind when building the bot is how will users know how to run the bot? I created the following method to solve that little problem. We are basically reading the readme file in our root directory and then returning all the data required to compose a message in slack. The following is added under our NetBot class from above.</p>
<p><code>get_network.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">send_help</span>(self):
        <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">        Sending all the help
</span><span style="color:#e6db74">        &#34;&#34;&#34;</span>
        <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;README.md&#34;</span>, <span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> reader:
            readme <span style="color:#f92672">=</span> reader<span style="color:#f92672">.</span>read()
        <span style="color:#66d9ef">return</span> {
            <span style="color:#e6db74">&#34;channels&#34;</span>: self<span style="color:#f92672">.</span>channel,
            <span style="color:#e6db74">&#34;filename&#34;</span>: <span style="color:#e6db74">&#34;README.md&#34;</span>,
            <span style="color:#e6db74">&#34;filetype&#34;</span>: <span style="color:#e6db74">&#34;markdown&#34;</span>,
            <span style="color:#e6db74">&#34;content&#34;</span>: readme,
        }
</code></pre></div><p><img src="/blog/images/netbot_help.png" alt="netbot help"></p>
<h2 id="interacting-with-network-devices">Interacting With Network Devices</h2>
<p>I mentioned before I wanted this to be multi-vendor. In this case we are going to use the popular NAPALM library!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">pip install napalm
pip install pyaoscx
pip install napalm-aruba-cx
</code></pre></div><p>In theory you can use whatever you are comfortable with; Netmiko, NAPALM, Nornir, or Ansible. As long as the end result returns data in a format we can use to craft a Slack message. For our networking example, we will use NAPALM to retrieve the network interfaces from two devices. One being a Cisco IOL router and the other an Aruba CX switch. Below you will see the two methods used to put this all together. The first is pretty standard syntax to connect to device, run the get_interfaces function, and return as pretty JSON.</p>
<p>Quick deviation, I almost forgot to mention the inventory. In a grander scale, you would most likely have some dynamic inventory that is cached to the app running or the code actually interacts with your inventory source to pull the correct information. Think Netbox API call to get device information for a device named XYZ. This would then have management IP, platform, and whatever else would be needed to connect using NAPALM. In our case we are using a very simple YAML file with the two hosts mentioned earlier.</p>
<p><code>hosts.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">
---
<span style="color:#f92672">R1</span>:
  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">R1</span>
  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">ios</span>
  <span style="color:#f92672">mgmt</span>: <span style="color:#e6db74">&#34;192.168.10.168&#34;</span>
<span style="color:#f92672">ArubaCX</span>:
  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ArubaCX</span>
  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">aoscx</span>
  <span style="color:#f92672">mgmt</span>: <span style="color:#e6db74">&#34;192.168.10.169&#34;</span>
</code></pre></div><p>As promised, here is the code that interacts with our network devices.</p>
<p><code>get_network.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_get_facts</span>(self):
        <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">        Method that will retrieve host facts
</span><span style="color:#e6db74">        &#34;&#34;&#34;</span>
        <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;hosts.yaml&#34;</span>, <span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> handle:
            host_root <span style="color:#f92672">=</span> safe_load(handle)

        driver <span style="color:#f92672">=</span> get_network_driver(host_root[<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>device_name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>][<span style="color:#e6db74">&#34;platform&#34;</span>])
        conn <span style="color:#f92672">=</span> driver(
            hostname<span style="color:#f92672">=</span>host_root[<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>device_name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>][<span style="color:#e6db74">&#34;mgmt&#34;</span>],
            username<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>USERNAME,
            password<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>PASSWORD,
        )
        conn<span style="color:#f92672">.</span>open()
        facts <span style="color:#f92672">=</span> conn<span style="color:#f92672">.</span>get_interfaces()
        my_file <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>dumps(facts, indent<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span>)
        <span style="color:#66d9ef">return</span> my_file
</code></pre></div><p>The second portion of interacting with the networking device is constructing the message. The following method will return a working format to post a file type message in Slack. Notice towards the bottom the “content” key is actually calling the “_get_facts” method above? The_get_facts method is just returning some pretty formatted JSON string that we can then pass into our method to build a slack message.</p>
<p><code>get_network.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_file_payload</span>(self):
        <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">        Method used to post files from data gathered on device
</span><span style="color:#e6db74">        &#34;&#34;&#34;</span>
        <span style="color:#66d9ef">return</span> {
            <span style="color:#e6db74">&#34;channels&#34;</span>: self<span style="color:#f92672">.</span>channel,
            <span style="color:#e6db74">&#34;filetype&#34;</span>: <span style="color:#e6db74">&#34;javascript&#34;</span>,
            <span style="color:#e6db74">&#34;content&#34;</span>: self<span style="color:#f92672">.</span>_get_facts(),
            <span style="color:#e6db74">&#34;filename&#34;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>device_name<span style="color:#e6db74">}</span><span style="color:#e6db74">-interfaces.json&#34;</span>,
        }
</code></pre></div><h2 id="running-the-application">Running the Application</h2>
<p>As mentioned previously, this code is heavily borrowed from Masons post! So much so that I left in the CoinBot learnings because it is an amazing reference and the code comments are great for learners. Ill cut out some of the extra information to just focus on the action, full code is available on my GitHub (linked below).</p>
<p><code>app.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">import</span> os
<span style="color:#f92672">import</span> logging
<span style="color:#f92672">from</span> flask <span style="color:#f92672">import</span> Flask
<span style="color:#f92672">from</span> slack <span style="color:#f92672">import</span> WebClient
<span style="color:#f92672">from</span> slackeventsapi <span style="color:#f92672">import</span> SlackEventAdapter
<span style="color:#f92672">from</span> coinbot <span style="color:#f92672">import</span> CoinBot
<span style="color:#f92672">from</span> get_network <span style="color:#f92672">import</span> NetBot

<span style="color:#75715e"># Initialize a Flask app to host the events adapter</span>
app <span style="color:#f92672">=</span> Flask(__name__)

<span style="color:#75715e"># Create an events adapter and register it to an endpoint in the slack app for event ingestion.</span>
slack_events_adapter <span style="color:#f92672">=</span> SlackEventAdapter(
    os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;SLACK_EVENTS_TOKEN&#34;</span>), <span style="color:#e6db74">&#34;/slack/events&#34;</span>, app
)

<span style="color:#75715e"># Initialize a Web API client</span>
slack_web_client <span style="color:#f92672">=</span> WebClient(token<span style="color:#f92672">=</span>os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;SLACK_TOKEN&#34;</span>))

<span style="color:#75715e"># Define bot ID so it will not respond to itself</span>
BOT_ID <span style="color:#f92672">=</span> slack_web_client<span style="color:#f92672">.</span>api_call(<span style="color:#e6db74">&#34;auth.test&#34;</span>)[<span style="color:#e6db74">&#34;user_id&#34;</span>]
</code></pre></div><p>Lets break that code down a bit. We will be utilizing Flask to do most of the heavy lifting. The imports mentioned above will import the required Flask, Slack, and NetBot packages. We utilize two environment variables for authentication. The BOT_ID constant is used in code later on to make sure the bot does not respond to itself. The next portions actually perform the execution of all of our code. I will break down one portion to keeps things short and sweet, but most all of them follow the same workflow.</p>
<p><code>app.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_network_info</span>(channel, device_name):
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    run the get_message_payload and get_file_upload method
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
    net_bot <span style="color:#f92672">=</span> NetBot(channel, device_name)
    my_message <span style="color:#f92672">=</span> net_bot<span style="color:#f92672">.</span>get_message_payload()
    file_output <span style="color:#f92672">=</span> net_bot<span style="color:#f92672">.</span>get_file_payload()
    slack_web_client<span style="color:#f92672">.</span>chat_postMessage(<span style="color:#f92672">**</span>my_message)
    slack_web_client<span style="color:#f92672">.</span>files_upload(<span style="color:#f92672">**</span>file_output)
</code></pre></div><p>We are creating a function called get_network_info that takes in two parameters. When this function is executed, it will instantiate an instance of NetBot, send a generic message, and upload the file created from the get_file_payload method.</p>
<p>The next portion of the code is used to handle how the bot interacts with messages seen or what is executed when a certain message is seen. From part one of Masons post, we subscribed the bot to events (and we filter on messages). Once we get a message we strip out certain portions that can then be reused to respond in the proper channel, react to a specific message (timestamp), and make sure the bot does not respond to itself.</p>
<p><code>app.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># When a &#39;message&#39; event is detected by the events adapter, forward that payload</span>
<span style="color:#75715e"># to this function.</span>
<span style="color:#a6e22e">@slack_events_adapter</span><span style="color:#f92672">.</span>on(<span style="color:#e6db74">&#34;message&#34;</span>)
<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">message</span>(payload):
    <span style="color:#e6db74">&#34;&#34;&#34;
</span><span style="color:#e6db74">    Parse the message event, and if the activation string is in the text,
</span><span style="color:#e6db74">    simulate something and send result
</span><span style="color:#e6db74">    &#34;&#34;&#34;</span>

    <span style="color:#75715e"># Get various portions of message</span>
    event <span style="color:#f92672">=</span> payload<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;event&#34;</span>, {})
    text <span style="color:#f92672">=</span> event<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;text&#34;</span>)
    user_id <span style="color:#f92672">=</span> event<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;user&#34;</span>)
    timestamp <span style="color:#f92672">=</span> event<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;ts&#34;</span>)
    channel_id <span style="color:#f92672">=</span> event<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;channel&#34;</span>)

    <span style="color:#75715e"># Making sure the bot doesnt respond to itself</span>
    <span style="color:#66d9ef">if</span> BOT_ID <span style="color:#f92672">!=</span> user_id:
</code></pre></div><p>The next piece of code is used to parse the messages that are seen by the bot. If a string matches one of the if clauses, some code will be executed. In this case we are looking for “netbot get network interfaces”. If you noticed in the readme, we are asking users to enter “netbot get network interfaces device=something”. The code will parse this output and strip “device=” to get the final device name. This will then be passed into the get_network_info function and execute the program.</p>
<p><code>app.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">        <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;netbot get network interfaces&#34;</span> <span style="color:#f92672">in</span> text<span style="color:#f92672">.</span>lower():
            full_text <span style="color:#f92672">=</span> text<span style="color:#f92672">.</span>split()
            device <span style="color:#f92672">=</span> full_text[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
            my_device <span style="color:#f92672">=</span> device<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;device=&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)
            slack_web_client<span style="color:#f92672">.</span>reactions_add(
                channel<span style="color:#f92672">=</span>channel_id, name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;robot_face&#34;</span>, timestamp<span style="color:#f92672">=</span>timestamp
            )
            slack_web_client<span style="color:#f92672">.</span>reactions_add(
                channel<span style="color:#f92672">=</span>channel_id, name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;rocket&#34;</span>, timestamp<span style="color:#f92672">=</span>timestamp
            )
            <span style="color:#66d9ef">return</span> get_network_info(channel_id, device_name<span style="color:#f92672">=</span>my_device)
</code></pre></div><p>You may have noticed a few commands the are prepend with “slack_web_client.reactions_add”. I wanted a way for the bot to respond to a user inputting a message that triggers the bot. Almost like a confirmation of message received. This can be message specific as I have made it for “netbot help” or “netbot get network interfaces”. Remember just a bit ago how we strip certain information from the message received like channel and timestamp ID? This is used throughout the code but here we are statically setting the types of reactions executed for this match. In the case of “get_network_info” the bot will react with a robot face and rocket.</p>
<p><img src="/blog/images/get_interfaces.png" alt="Get Interfaces"></p>
<p>The rest of the code is boilerplate to execute the application and enable logging. No changes from the original post at Digital Ocean. I want to thank Mason for the amazing blog post to kick start this spark for me. Thank you for reading this far, really means a lot! I hope what I’ve written makes a bit of sense and maybe inspires you to create something awesome.</p>
<h2 id="bugsissues">Bugs/Issues</h2>
<ul>
<li>Bot reruns get network interface code even when new message is not sent (not seen when reactions are used).</li>
<li>When reaction is used multiple runs are not seen but app reports error 500 (already reacted).</li>
</ul>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://unsplash.com/photos/HBGYvOKXu8A">Featured Image by Jason Leung</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-build-a-slackbot-in-python-on-ubuntu-20-04">How To Build a Slackbot in Python on Ubuntu 20.04</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLzMcBGfZo4-kqyzTzJWCV6lyK-ZMYECDc">Python Slack Bot Tutorial Playlist by Tech With Tim</a></li>
<li><a href="https://github.com/JulioPDX/ne_bot_example">GitHub Repository</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Route Redistribution OSPFv3 and EIGRPv6</title>
            <link>https://juliopdx.com/2021/05/03/route-redistribution-ospfv3-and-eigrpv6/</link>
            <pubDate>Mon, 03 May 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/05/03/route-redistribution-ospfv3-and-eigrpv6/</guid>
            <description>Introduction Hello and thank you for checking out this post. I was recently working through OSPF and EIGRP for ENARSI studies and something came up on a lab I was building. Redistributing routes between OSPFv3 and EIGRPv6. I am specifically speaking of the IPv6 variant of these protocols.
In the topology you see below. We have three routing devices. One running only EIGRPv6, one running OSPFv3 (Aruba CX), and the redistribution node running both protocols.</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello and thank you for checking out this post. I was recently working through OSPF and EIGRP for ENARSI studies and something came up on a lab I was building. Redistributing routes between OSPFv3 and EIGRPv6. I am specifically speaking of the IPv6 variant of these protocols.</p>
<p>In the topology you see below. We have three routing devices. One running only EIGRPv6, one running OSPFv3 (Aruba CX), and the redistribution node running both protocols. Both protocols can form neighbors using only link local addressing, so we wont see any global IPv6 addresses on those links.</p>
<p><img src="/blog/images/ospftoeigrp.png" alt="OSPF to EIGRP"></p>
<h2 id="ospfv3">OSPFv3</h2>
<p>The OSPFv3 node is an Aruba CX switch running …OSPFv3! I have a few loopbacks configured so we can add some networks for testing. All of OSPF will be in area 0 for simplicity. We set all interfaces to passive besides the uplink to REDIS. Configuration snippet is below.</p>
<p><code>OSPFv3 Node Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">hostname OSPFv3
!
interface 1/1/1
    no shutdown
    description to REDIS
    ipv6 address link-local fe80::3/64
    ipv6 ospfv3 1 area 0.0.0.0
    no ipv6 ospfv3 passive
    ipv6 ospfv3 network point-to-point
interface loopback 0
    ipv6 address link-local fe80::3/64
    ipv6 address 2001:db8:456:4::1/64
    ipv6 ospfv3 1 area 0.0.0.0
interface loopback 1
    ipv6 address link-local fe80::3/64
    ipv6 address 2001:db8:456:5::1/64
    ipv6 ospfv3 1 area 0.0.0.0
interface loopback 2
    ipv6 address link-local fe80::3/64
    ipv6 address 2001:db8:456:6::1/64
    ipv6 ospfv3 1 area 0.0.0.0
!
router ospfv3 1
    router-id 3.3.3.3
    passive-interface default
    area 0.0.0.0
!
</code></pre></div><h2 id="eigrpv6">EIGRPv6</h2>
<p>In the case of EIGRPv6, we will be using named EIGRP with an address family of IPv6. We add a few loopbacks for testing as well. One difference you’ll see in the configurations, EIGRPv6 is enabled on all interfaces with IPv6 addresses. There is no specific enablement of an interface to run EIGRPv6 or network statements. Configuration snippet is below.</p>
<p><code>EIGRPv6 Node Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">hostname EIGRPv6
!
ipv6 unicast-routing
!
interface Loopback0
 no ip address
 ipv6 address FE80::1 link-local
 ipv6 address 2001:DB8:123:1::1/64
!
interface Loopback1
 no ip address
 ipv6 address FE80::1 link-local
 ipv6 address 2001:DB8:123:2::1/64
!
interface Loopback2
 no ip address
 ipv6 address FE80::1 link-local
 ipv6 address 2001:DB8:123:3::1/64
!
interface GigabitEthernet0/0
 description to REDIS
 no ip address
 ipv6 address FE80::1 link-local
!
router eigrp JULIOPDX
 !
 address-family ipv6 unicast autonomous-system 123
  !
  topology base
  exit-af-topology
  eigrp router-id 1.1.1.1
 exit-address-family
</code></pre></div><h2 id="redis">REDIS</h2>
<p>This node is special, it gets to run two routing protocols and perform redistribution between them! The configuration is very basic indeed. Only two interfaces set for link local addresses. Oh remember when I mentioned EIGRPv6 is enabled for all IPv6 interfaces? I will shutdown the interface towards OSPFv3. Good to reduce all the chatter. Configuration snippet is below.</p>
<p><code>REDIS Node Initial Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">hostname REDIS
!
ipv6 unicast-routing
!
interface GigabitEthernet0/0
 description to EIGRPv6
 no ip address
 ipv6 address FE80::2 link-local
!
interface GigabitEthernet0/1
 description to OSPFv3
 no ip address
 ipv6 address FE80::2 link-local
 ospfv3 network point-to-point
 ospfv3 1 ipv6 area 0
!
router eigrp JULIOPDX
 !
 address-family ipv6 unicast autonomous-system 123
  !
  af-interface GigabitEthernet0/1
   shutdown
  exit-af-interface
  !
  topology base
  exit-af-topology
  eigrp router-id 2.2.2.2
 exit-address-family
!
router ospfv3 1
 router-id 2.2.2.2
 !
 address-family ipv6 unicast
  passive-interface default
  no passive-interface GigabitEthernet0/1
 exit-address-family
</code></pre></div><h2 id="neighbors-and-routes">Neighbors and Routes</h2>
<p><code>EIGRPv6 Neighbor</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">REDIS#show ipv6 eigrp neighbors
EIGRP-IPv6 VR(JULIOPDX) Address-Family Neighbors for AS(123)
H   Address                 Interface              Hold Uptime   SRTT   RTO  Q  Seq
                                                    (sec)         (ms)       Cnt Num
0   Link-local address:     Gi0/0                    11 00:23:40  275  1650  0  11
     FE80::1
REDIS#
</code></pre></div><p><code>EIGRPv6 Routes</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">REDIS#show ipv6 route eigrp
D   2001:DB8:123:1::/64 [90/10880]
     via FE80::1, GigabitEthernet0/0
D   2001:DB8:123:2::/64 [90/10880]
     via FE80::1, GigabitEthernet0/0
D   2001:DB8:123:3::/64 [90/10880]
     via FE80::1, GigabitEthernet0/0
REDIS#
</code></pre></div><p><code>OSPFv3 Neighbor</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">REDIS#show ipv6 ospf neighbor
        OSPFv3 Router with ID (2.2.2.2) (Process ID 1)
Neighbor ID     Pri   State           Dead Time   Interface ID    Interface
3.3.3.3           0   FULL/  -        00:00:35    1360007168      GigabitEthernet0/1
REDIS#
#### Notice, no DR or BDR due to the point to point network
</code></pre></div><p><code>OSPFv3 Routes</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">REDIS#show ipv6 route ospf
O   2001:DB8:456:4::1/128 [110/1]
     via FE80::3, GigabitEthernet0/1
O   2001:DB8:456:5::1/128 [110/1]
     via FE80::3, GigabitEthernet0/1
O   2001:DB8:456:6::1/128 [110/1]
     via FE80::3, GigabitEthernet0/1
REDIS#
</code></pre></div><p>I wont bother sharing the neighbor states of EIGRPv6 or OSPFv3, but below you will see their routes before redistribution.</p>
<p><code>Routes on Nodes</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">#### EIGRPv6 node first
EIGRPv6#show ipv6 route eigrp
EIGRPv6#
##### Now for OSPFv3 node
OSPFv3# show ipv6 route ospf
No ipv6 routes configured
OSPFv3#
</code></pre></div><h2 id="the-redistribution">The Redistribution</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">REDIS#show run | s router
router eigrp JULIOPDX
 !
 address-family ipv6 unicast autonomous-system 123
  !
  topology base
   redistribute ospf 1 metric 1000000 1 255 1 1500
  exit-af-topology
 exit-address-family
router ospfv3 1
 address-family ipv6 unicast
  redistribute eigrp 123
 exit-address-family
</code></pre></div><p><code>New Routes on OSPFv3 and EIGRPv6</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">EIGRPv6#show ipv6 route eigrp
EX  2001:DB8:456:4::1/128 [170/15360]
     via FE80::2, GigabitEthernet0/0
EX  2001:DB8:456:5::1/128 [170/15360]
     via FE80::2, GigabitEthernet0/0
EX  2001:DB8:456:6::1/128 [170/15360]
     via FE80::2, GigabitEthernet0/0
EIGRPv6#

OSPFv3# show ipv6 route ospf
2001:db8:123:1::/64, vrf default
        via  fe80::2%1/1/1,  [110/20],  ospf
2001:db8:123:2::/64, vrf default
        via  fe80::2%1/1/1,  [110/20],  ospf
2001:db8:123:3::/64, vrf default
        via  fe80::2%1/1/1,  [110/20],  ospf
OSPFv3#
</code></pre></div><h2 id="simple-ping-and-trace">Simple Ping and Trace</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">EIGRPv6#ping 2001:db8:456:4::1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 2001:DB8:456:4::1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 13/18/27 ms
EIGRPv6#traceroute 2001:db8:456:4::1
Type escape sequence to abort.
Tracing the route to 2001:DB8:456:4::1
  1 FE80::2 39 msec 15 msec 16 msec
  2 2001:DB8:456:4::1 13 msec 19 msec 19 msec
EIGRPv6#

OSPFv3# ping6 2001:db8:123:1::1 repetitions 1
PING 2001:db8:123:1::1(2001:db8:123:1::1) 100 data bytes
108 bytes from 2001:db8:123:1::1: icmp_seq=1 ttl=63 time=21.0 ms

--- 2001:db8:123:1::1 ping statistics ---
 1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 21.016/21.016/21.016/0.000 ms
OSPFv3# traceroute6 2001:db8:123:1::1
traceroute to 2001:db8:123:1::1 (2001:db8:123:1::1) from 2001:db8:456:4::1, 30 hops max, 3 sec. timeout, 3 probes, 24 byte packets
 1  fe80::2 (fe80::2)  18.681 ms  13.902 ms  15.768 ms
 2  2001:db8:123:1::1 (2001:db8:123:1::1)  20.081 ms  15.943 ms  19.944 ms
OSPFv3#
</code></pre></div><p>Thank you for reading this far, I really do appreciate it. I wish you the best and I hope you find something useful or interesting in this post. Cheers!</p>
]]></content>
        </item>
        
        <item>
            <title>Simple Radius Server in EVE-NG</title>
            <link>https://juliopdx.com/2021/04/16/simple-radius-server-in-eve-ng/</link>
            <pubDate>Fri, 16 Apr 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/04/16/simple-radius-server-in-eve-ng/</guid>
            <description>Introduction I was recently going through an advanced routing course on Pluralsight by Nick Russo, great course by the way. Check it out HERE. During the course we inevitably get to the services portion. The portion most of us dread as network engineers. This is all the extra stuff. Where the routing and switching might be the sexy topic, services is arguably not that exciting… but very much necessary.
If we are in the process of testing AAA, we will need some form of TACACS or RADIUS server.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/freeradius-new.png" alt="Topology"></p>
<h2 id="introduction">Introduction</h2>
<p>I was recently going through an advanced routing course on Pluralsight by Nick Russo, great course by the way. Check it out HERE. During the course we inevitably get to the services portion. The portion most of us dread as network engineers. This is all the extra stuff. Where the routing and switching might be the sexy topic, services is arguably not that exciting… but very much necessary.</p>
<p>If we are in the process of testing AAA, we will need some form of TACACS or RADIUS server. Deploying a full on RADIUS VM can definitely eat up a lot of resources. GNS3, for example, has an appliance that can be added to provide this functionality. To my knowledge, EVE-NG does not. We will be leveraging FreeRADIUS on a lightweight Ubuntu VM. If you want to learn more about FreeRADIUS, check out the link mentioned below.</p>
<p>I hope to keep this as simple as possible to help the next engineer that has to stand up a simple RADIUS server in their topologies. I will have a few assumptions in this writing. For example, I assume you have some knowledge of using EVE-NG, adding Linux nodes, and most importantly AAA. I will include configurations of the Cisco nodes and useful links at the end of the post!</p>
<h2 id="topology">Topology</h2>
<p>The topology you see above is pretty bare bones. Two nodes running OSPF and all the networks in area 0. One Linux machine will be used for SSH testing and the other is the FreeRADIUS server (Ubuntu 18.04). I linked the image used above but you can use any standard Ubuntu 18.04 image to run FreeRADIUS.</p>
<h2 id="freeradius">FreeRADIUS</h2>
<p>If your topology already has internet access, you may be able to skip a few of these steps. In my case, this topology is isolated. Start by adding a network connection, this will connect the server to my internal network and give the server access to the internet.</p>
<p>Within the VM console session, run the following commands:</p>
<p><code>FreeRADIUS Install</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo apt-get update
sudo apt-get install freeradius -y
</code></pre></div><h2 id="static-ip-for-ubuntu-server">Static IP for Ubuntu Server</h2>
<p>At this point, the server has FreeRADIUS installed. Now we connect the server as shown in the topology image above and assign a static IP address. In Ubuntu 18.04 this can be done by editing the /etc/netplan/01-netcfg.yaml file. You can edit this file using vi, vim, or nano. Doesn&rsquo;t really matter. I believe vi and nano come pre installed on this image. Edit that file and save. Example below.</p>
<p><code>/etc/netplan/01-netcfg.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml"><span style="color:#f92672">network</span>:
   <span style="color:#f92672">version</span>: <span style="color:#ae81ff">2</span>
   <span style="color:#f92672">renderer</span>: <span style="color:#ae81ff">networkd</span>
   <span style="color:#f92672">ethernets</span>:
     <span style="color:#f92672">ens3</span>:
       <span style="color:#f92672">dhcp4</span>: <span style="color:#66d9ef">no</span>
       <span style="color:#f92672">addresses</span>:
         - <span style="color:#ae81ff">192.168.2.2</span><span style="color:#ae81ff">/24</span>
       <span style="color:#f92672">gateway4</span>: <span style="color:#ae81ff">192.168.2.1</span>
</code></pre></div><p>Once that file is saved, run the command below to apply the configuration.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo netplan apply
</code></pre></div><h2 id="add-clients-and-users">Add Clients and Users</h2>
<p>The next step isn’t too bad. You essentially have to modify two files. One for user logins and the other for clients (network devices). The files have a lot of examples and working options. Most all of it is commented out. I’ll include just the portions that are active. Again, feel free to modify this with whatever editor you are comfortable with.</p>
<p><code>/etc/freeradius/3.0/clients.conf</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">client vIOS1 {
    ipaddr = 10.0.0.1
    secret = freeradius
}
client vIOS2 {
    ipaddr = 10.0.0.2
    secret = freeradius
}
</code></pre></div><p><code>/etc/freeradius/3.0/mods-config/files/authorize</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">Julio    Cleartext-Password := &#34;PDX&#34;
         Reply-Message = &#34;Welcome to the world of tomorrow&#34;,
         cisco-avpair := &#34;shell:priv-lvl=15&#34;
</code></pre></div><p>Once that is completed, run the following commands to activate FreeRADIUS:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">sudo service freeradius stop
sudo freeradius -X
</code></pre></div><h2 id="ssh-validation">SSH Validation</h2>
<p><img src="/blog/images/ssh_test_radius.png" alt="SSH Test"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Overall I don’t think it was too much work to get this going. If you build in some automation on the radius server, you could populate all the client entries with Ansible or Python. I think someone with a bit more brain power could probably create a pre built image that has FreeRADIUS pre packaged as an EVE-NG appliance. Thank you for reading this far and I hope this helps you down the road. Best of luck!</p>
<ul>
<li><a href="https://www.eve-ng.net/index.php/documentation/howtos/howto-create-own-linux-host-image/">How to add Linux images to EVE-NG</a></li>
<li><a href="https://ipnet.xyz/2018/06/ubuntu-image-for-eve-ng-python-for-network-engineers/">Ubuntu 18.04 image used in lab, can be generic as well</a></li>
<li><a href="https://freeradius.org/">FreeRADIUS Documentation</a></li>
<li><a href="https://linuxize.com/post/how-to-configure-static-ip-address-on-ubuntu-18-04/">Configure static addresses on Ubuntu 18.04</a></li>
<li><a href="/blog/files/vIOS1.txt">vIOS1 Configuration</a></li>
<li><a href="/blog/files/vIOS2.txt">vIOS2 Configuration</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Automating Multi Vendor Environments With Netmiko</title>
            <link>https://juliopdx.com/2021/04/02/automating-multi-vendor-environments-with-netmiko/</link>
            <pubDate>Fri, 02 Apr 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/04/02/automating-multi-vendor-environments-with-netmiko/</guid>
            <description>Introduction Hello again! I was working through Nick Russos’ awesome Automating Networks with Python course on Pluralsight, check it out HERE. In the course, Nick does a great job of breaking down the code and the functionality of each bit. I’m still doing a lot of leaning in Python so please bear with me.
The course uses an MPLS environment as an example of network automation with Python. I decided to use the principles taught in the Netmiko portion of the course to try and automate some OSPF between multiple vendors.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/multi_ospf.png" alt="Multi Routers"></p>
<h2 id="introduction">Introduction</h2>
<p>Hello again! I was working through Nick Russos’ awesome Automating Networks with Python course on Pluralsight, check it out HERE. In the course, Nick does a great job of breaking down the code and the functionality of each bit. I’m still doing a lot of leaning in Python so please bear with me.</p>
<p>The course uses an MPLS environment as an example of network automation with Python. I decided to use the principles taught in the Netmiko portion of the course to try and automate some OSPF between multiple vendors. As you can see above, we are featuring Arista, Aruba, and Cisco. The topology is nothing crazy. All router facing ports are in area 0 and sharing routes. Every router will have a loopback interface to host a network. The only other caveat is that interfaces are passive by default and we enable each interface separately.</p>
<h2 id="host-variables">Host Variables</h2>
<p>Each host has its own variable file. I tried to use something that made sense to me. There’s probably a better way to organize this file, it works, so I can dig it. Below is a sample of the Arista vEOS node. Each one is essentially the same besides two or three variables.</p>
<p><code>vEOS.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">vEOS</span>
<span style="color:#f92672">interfaces</span>:
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Ethernet1</span>
    <span style="color:#f92672">ip</span>: <span style="color:#ae81ff">10.0.0.2</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">ospf</span>:
      <span style="color:#f92672">process</span>: <span style="color:#ae81ff">1</span>
      <span style="color:#f92672">area</span>: <span style="color:#ae81ff">0</span>
      <span style="color:#f92672">passive</span>: <span style="color:#66d9ef">False</span>
  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Loopback0</span>
    <span style="color:#f92672">ip</span>: <span style="color:#ae81ff">192.168.2.1</span><span style="color:#ae81ff">/24</span>
    <span style="color:#f92672">ospf</span>:
      <span style="color:#f92672">process</span>: <span style="color:#ae81ff">1</span>
      <span style="color:#f92672">area</span>: <span style="color:#ae81ff">1</span>
<span style="color:#f92672">ospf</span>:
  <span style="color:#f92672">process</span>: <span style="color:#ae81ff">1</span>
  <span style="color:#f92672">router_id</span>: <span style="color:#ae81ff">2.2.2.2</span>
  <span style="color:#f92672">area</span>:
    - <span style="color:#ae81ff">0</span>
    - <span style="color:#ae81ff">1</span>
</code></pre></div><h2 id="ospf-and-jinja">OSPF and Jinja</h2>
<p>The jinja file process isn’t too bad. If you know loops, if, and else statements, you’ll be just fine. I’m pretty familiar with Aruba and Cisco but not so much with Arista. Lucky for me Arista is eerily similar to Cisco Syntax. I generally work my way through configuring the devices manually once and then convert the configurations to a template. Below is the Aruba version of the jinja template, each vendor gets its own jinja file.</p>
<p><code>hp_procurve.j2</code></p>
<pre tabindex="0"><code class="language-jinja2" data-lang="jinja2">{% if data.ospf %}
router ospf {{ data.ospf.process }}
    router-id {{ data.ospf.router_id }}
    passive-interface default
{% for area in data.ospf.areas %}
    area {{ area }}
{% endfor %}
{% for int in data.interfaces %}
{% if int.ospf %}
interface {{ int.name }}
{% if &quot;back&quot; not in int.name %}
    no shutdown
{% endif%}
    ip address {{ int.ip }}
    ip ospf {{ int.ospf.process }} area {{ int.ospf.area }}
{% if int.ospf.passive == False %}
    no ip ospf passive
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
copy running-config startup-config
</code></pre><h2 id="the-script">The Script</h2>
<p>I wont go too deep in breaking down the file as I think it would take a while and I would really recommend you check out Nicks’ course I linked above. If I were to break down the script at a high level, below are the steps that are being performed.</p>
<ul>
<li>Import the required libraries.</li>
<li>Define functions for address management (more on this later).</li>
<li>Define the main function to load in host variables, jinja environment, and loop over each host.</li>
<li>Bonus, network backup task (why not?).</li>
</ul>
<p><code>ospf_netmiko.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e">#! /usr/bin/env python</span>

<span style="color:#75715e"># Import requirements</span>
<span style="color:#f92672">from</span> yaml <span style="color:#f92672">import</span> safe_load
<span style="color:#f92672">from</span> netmiko <span style="color:#f92672">import</span> Netmiko
<span style="color:#f92672">from</span> jinja2 <span style="color:#f92672">import</span> Environment, FileSystemLoader
<span style="color:#f92672">from</span> netaddr <span style="color:#f92672">import</span> IPNetwork
<span style="color:#f92672">from</span> rich <span style="color:#f92672">import</span> print <span style="color:#66d9ef">as</span> pr

<span style="color:#75715e"># functions to be used in jinja templates for IP management</span>
<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">address</span>(a):
    a <span style="color:#f92672">=</span> str(IPNetwork(a)<span style="color:#f92672">.</span>ip)
    <span style="color:#66d9ef">return</span> a


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">mask</span>(b):
    b <span style="color:#f92672">=</span> str(IPNetwork(b)<span style="color:#f92672">.</span>netmask)
    <span style="color:#66d9ef">return</span> b


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():

    <span style="color:#75715e"># Open hosts file as variable for future use</span>
    <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;hosts.yaml&#34;</span>, <span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> handle:
        host_root <span style="color:#f92672">=</span> safe_load(handle)
    pr(host_root)

    <span style="color:#75715e"># Set platform map to match netmiko</span>
    platform_map <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;ios&#34;</span>: <span style="color:#e6db74">&#34;cisco_ios&#34;</span>, <span style="color:#e6db74">&#34;arista&#34;</span>: <span style="color:#e6db74">&#34;arista_eos&#34;</span>, <span style="color:#e6db74">&#34;aruba&#34;</span>: <span style="color:#e6db74">&#34;hp_procurve&#34;</span>}

    <span style="color:#75715e"># Assigning platform variable to each host</span>
    <span style="color:#66d9ef">for</span> host <span style="color:#f92672">in</span> host_root[<span style="color:#e6db74">&#34;host_list&#34;</span>]:
        platform <span style="color:#f92672">=</span> platform_map[host[<span style="color:#e6db74">&#34;platform&#34;</span>]]

        <span style="color:#75715e"># Load in the host specific vars</span>
        <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;vars/</span><span style="color:#e6db74">{</span>host[<span style="color:#e6db74">&#39;name&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">.yaml&#34;</span>, <span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> handle:
            ospf <span style="color:#f92672">=</span> safe_load(handle)

        <span style="color:#75715e"># This portion is essentially configuring our jinja environment</span>
        j2_env <span style="color:#f92672">=</span> Environment(
            loader<span style="color:#f92672">=</span>FileSystemLoader(<span style="color:#e6db74">&#34;.&#34;</span>), trim_blocks<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, autoescape<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>
        )
        <span style="color:#75715e"># https://www.kite.com/python/answers/how-to-call-a-function-in-a-jinja2-template-in-python</span>
        j2_env<span style="color:#f92672">.</span>globals[<span style="color:#e6db74">&#34;address&#34;</span>] <span style="color:#f92672">=</span> address
        j2_env<span style="color:#f92672">.</span>globals[<span style="color:#e6db74">&#34;mask&#34;</span>] <span style="color:#f92672">=</span> mask

        template <span style="color:#f92672">=</span> j2_env<span style="color:#f92672">.</span>get_template(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;templates/netmiko/</span><span style="color:#e6db74">{</span>platform<span style="color:#e6db74">}</span><span style="color:#e6db74">.j2&#34;</span>)
        new_ospf_config <span style="color:#f92672">=</span> template<span style="color:#f92672">.</span>render(data<span style="color:#f92672">=</span>ospf)
        pr(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">{</span>new_ospf_config<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)

        conn <span style="color:#f92672">=</span> Netmiko(
            host<span style="color:#f92672">=</span>host[<span style="color:#e6db74">&#34;mgmt&#34;</span>],
            username<span style="color:#f92672">=</span>host[<span style="color:#e6db74">&#34;username&#34;</span>],
            password<span style="color:#f92672">=</span>host[<span style="color:#e6db74">&#34;password&#34;</span>],
            device_type<span style="color:#f92672">=</span>platform,
        )

        pr(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">#### Logged into </span><span style="color:#e6db74">{</span>conn<span style="color:#f92672">.</span>find_prompt()<span style="color:#e6db74">}</span><span style="color:#e6db74">, woohoo! ####&#34;</span>)

        result <span style="color:#f92672">=</span> conn<span style="color:#f92672">.</span>send_config_set(new_ospf_config<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>))

        print(result)

        <span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;backups/</span><span style="color:#e6db74">{</span>host[<span style="color:#e6db74">&#39;name&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">.conf&#34;</span>, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> writer:
            result <span style="color:#f92672">=</span> conn<span style="color:#f92672">.</span>send_command(<span style="color:#e6db74">&#34;show run&#34;</span>)
            writer<span style="color:#f92672">.</span>writelines(result)

        conn<span style="color:#f92672">.</span>disconnect()


<span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;__main__&#34;</span>:
    main()
</code></pre></div><h2 id="bonus-the-ansibilism">Bonus, The Ansibilism</h2>
<p>Something you will run into when diving into network automation is address management. Hmm or should I say address format conversion. Some network operating systems accept the format of “192.168.1.1/24” for address assignment and others accept “192.168.1.1 255.255.255.255.0”. My background is more in Ansible and I would usually do something like the following.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">address <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;192.168.1.1/24&#34;</span>
<span style="color:#75715e"># Jinja</span>
interface gig0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
   ip address {{ address <span style="color:#f92672">|</span> ipaddr(<span style="color:#e6db74">&#39;address&#39;</span>) }} {{ address <span style="color:#f92672">|</span> ipaddr(<span style="color:#e6db74">&#39;netmask&#39;</span>) }}
   no shutdown
</code></pre></div><p>I was curious if something similar was available when using Jinja in Python. Honestly, I’m not sure and I bet there’s an easier way. If there is please let me know, and tell me “this is the way”. Long story short, this led me to the link included in the script and a bit of time on the python interpreter for testing. My testing went something like the following.</p>
<p><code>testing</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">In [<span style="color:#ae81ff">1</span>]: <span style="color:#f92672">from</span> netaddr <span style="color:#f92672">import</span> <span style="color:#f92672">*</span>
In [<span style="color:#ae81ff">2</span>]: a <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;192.168.1.1/24&#34;</span>
In [<span style="color:#ae81ff">3</span>]: str(IPNetwork(a)<span style="color:#f92672">.</span>ip)
Out[<span style="color:#ae81ff">3</span>]: <span style="color:#e6db74">&#39;192.168.1.1&#39;</span>
In [<span style="color:#ae81ff">4</span>]: str(IPNetwork(a)<span style="color:#f92672">.</span>netmask)
Out[<span style="color:#ae81ff">4</span>]: <span style="color:#e6db74">&#39;255.255.255.0&#39;</span>
In [<span style="color:#ae81ff">5</span>]:
</code></pre></div><p>Now that I had the format, I created two functions, one for the address portion and one for the netmask. Ill show the relevant snippets from all the pieces involved below!</p>
<p><code>Odds and Ends</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#f92672">from</span> netaddr <span style="color:#f92672">import</span> IPNetwork

<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">address</span>(a):
    a <span style="color:#f92672">=</span> str(IPNetwork(a)<span style="color:#f92672">.</span>ip)
    <span style="color:#66d9ef">return</span> a


<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">mask</span>(b):
    b <span style="color:#f92672">=</span> str(IPNetwork(b)<span style="color:#f92672">.</span>netmask)
    <span style="color:#66d9ef">return</span> b

<span style="color:#75715e"># https://www.kite.com/python/answers/how-to-call-a-function-in-a-jinja2-template-in-python</span>
        j2_env<span style="color:#f92672">.</span>globals[<span style="color:#e6db74">&#34;address&#34;</span>] <span style="color:#f92672">=</span> address
        j2_env<span style="color:#f92672">.</span>globals[<span style="color:#e6db74">&#34;mask&#34;</span>] <span style="color:#f92672">=</span> mask

<span style="color:#75715e"># From Template</span>
ip address {{ address(int<span style="color:#f92672">.</span>ip) }} {{ mask(int<span style="color:#f92672">.</span>ip) }}
</code></pre></div><h2 id="script-output">Script Output</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">{
     <span style="color:#e6db74">&#39;host_list&#39;</span>: [
         {
             <span style="color:#e6db74">&#39;name&#39;</span>: <span style="color:#e6db74">&#39;vIOS&#39;</span>,
             <span style="color:#e6db74">&#39;platform&#39;</span>: <span style="color:#e6db74">&#39;ios&#39;</span>,
             <span style="color:#e6db74">&#39;mgmt&#39;</span>: <span style="color:#e6db74">&#39;192.168.10.122&#39;</span>,
             <span style="color:#e6db74">&#39;username&#39;</span>: <span style="color:#e6db74">&#39;cisco&#39;</span>,
             <span style="color:#e6db74">&#39;password&#39;</span>: <span style="color:#e6db74">&#39;cisco&#39;</span>
         },
         {
             <span style="color:#e6db74">&#39;name&#39;</span>: <span style="color:#e6db74">&#39;ArubaCX&#39;</span>,
             <span style="color:#e6db74">&#39;platform&#39;</span>: <span style="color:#e6db74">&#39;aoscx&#39;</span>,
             <span style="color:#e6db74">&#39;mgmt&#39;</span>: <span style="color:#e6db74">&#39;192.168.10.142&#39;</span>,
             <span style="color:#e6db74">&#39;username&#39;</span>: <span style="color:#e6db74">&#39;admin&#39;</span>,
             <span style="color:#e6db74">&#39;password&#39;</span>: <span style="color:#e6db74">&#39;aruba&#39;</span>
         },
         {
             <span style="color:#e6db74">&#39;name&#39;</span>: <span style="color:#e6db74">&#39;vEOS&#39;</span>,
             <span style="color:#e6db74">&#39;platform&#39;</span>: <span style="color:#e6db74">&#39;eos&#39;</span>,
             <span style="color:#e6db74">&#39;mgmt&#39;</span>: <span style="color:#e6db74">&#39;192.168.10.151&#39;</span>,
             <span style="color:#e6db74">&#39;username&#39;</span>: <span style="color:#e6db74">&#39;admin&#39;</span>,
             <span style="color:#e6db74">&#39;password&#39;</span>: <span style="color:#e6db74">&#39;arista&#39;</span>
         }
     ]
 }
 Configuration to be loaded on vIOS:
 router ospf <span style="color:#ae81ff">1</span>
     router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">3.3.3.3</span>
     passive<span style="color:#f92672">-</span>interface default
     no passive<span style="color:#f92672">-</span>interface GigabitEthernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
 interface GigabitEthernet0<span style="color:#f92672">/</span><span style="color:#ae81ff">0</span>
     no shutdown
     ip address <span style="color:#ae81ff">10.0.0.3</span> <span style="color:#ae81ff">255.255.255.0</span>
     ip ospf <span style="color:#ae81ff">1</span> area <span style="color:#ae81ff">0</span>
 interface Loopback0
     ip address <span style="color:#ae81ff">192.168.3.1</span> <span style="color:#ae81ff">255.255.255.0</span>
     ip ospf <span style="color:#ae81ff">1</span> area <span style="color:#ae81ff">1</span>
 do wr
 Logged into vIOS<span style="color:#75715e">#, woohoo!</span>
 configure terminal
 Enter configuration commands, one per line<span style="color:#f92672">.</span>  End <span style="color:#66d9ef">with</span> CNTL<span style="color:#f92672">/</span>Z<span style="color:#f92672">.</span>
 vIOS(config)<span style="color:#75715e">#router ospf 1</span>
 vIOS(config<span style="color:#f92672">-</span>router)<span style="color:#75715e">#    router-id 3.3.3.3</span>
 vIOS(config<span style="color:#f92672">-</span>router)<span style="color:#75715e">#    passive-interface default</span>
 vIOS(config<span style="color:#f92672">-</span>router)<span style="color:#75715e">#    no passive-interface GigabitEthernet0/0</span>
 vIOS(config<span style="color:#f92672">-</span>router)<span style="color:#75715e">#interface GigabitEthernet0/0</span>
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#    no shutdown</span>
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#    ip address 10.0.0.3 255.255.255.0</span>
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#    ip ospf 1 area 0</span>
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#interface Loopback0</span>
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#    ip address 192.168.3.1 255.255.255.0</span>
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#    ip ospf 1 area 1</span>
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#do wr</span>
 Building configuration<span style="color:#960050;background-color:#1e0010">…</span>
 [OK]
 vIOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#end</span>
 vIOS<span style="color:#75715e">#</span>
 Configuration to be loaded on ArubaCX:
 router ospf <span style="color:#ae81ff">1</span>
     router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">1.1.1.1</span>
     passive<span style="color:#f92672">-</span>interface default
     area <span style="color:#ae81ff">0.0.0.0</span>
     area <span style="color:#ae81ff">0.0.0.1</span>
 interface <span style="color:#ae81ff">1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1</span>
     no shutdown
     ip address <span style="color:#ae81ff">10.0.0.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
     ip ospf <span style="color:#ae81ff">1</span> area <span style="color:#ae81ff">0.0.0.0</span>
     no ip ospf passive
 interface loopback <span style="color:#ae81ff">0</span>
     ip address <span style="color:#ae81ff">192.168.1.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
     ip ospf <span style="color:#ae81ff">1</span> area <span style="color:#ae81ff">0.0.0.1</span>
 copy running<span style="color:#f92672">-</span>config startup<span style="color:#f92672">-</span>config
 Logged into ArubaCX<span style="color:#75715e">#, woohoo!</span>
 configure terminal
 ArubaCX(config)<span style="color:#75715e"># router ospf 1</span>
 ArubaCX(config<span style="color:#f92672">-</span>ospf<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#75715e">#     router-id 1.1.1.1</span>
 ArubaCX(config<span style="color:#f92672">-</span>ospf<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#75715e">#     passive-interface default</span>
 ArubaCX(config<span style="color:#f92672">-</span>ospf<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#75715e">#     area 0.0.0.0</span>
 ArubaCX(config<span style="color:#f92672">-</span>ospf<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#75715e">#     area 0.0.0.1</span>
 ArubaCX(config<span style="color:#f92672">-</span>ospf<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#75715e"># interface 1/1/1</span>
 ArubaCX(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#     no shutdown</span>
 ArubaCX(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#     ip address 10.0.0.1/24</span>
 ArubaCX(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#     ip ospf 1 area 0.0.0.0</span>
 ArubaCX(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#     no ip ospf passive</span>
 ArubaCX(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e"># interface loopback 0</span>
 ArubaCX(config<span style="color:#f92672">-</span>loopback<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#     ip address 192.168.1.1/24</span>
 ArubaCX(config<span style="color:#f92672">-</span>loopback<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e">#     ip ospf 1 area 0.0.0.1</span>
 ArubaCX(config<span style="color:#f92672">-</span>loopback<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e"># copy running-config startup-config</span>
 Copying configuration: []
 Copying configuration: [<span style="color:#f92672">|</span>]
 Copying configuration: [<span style="color:#f92672">/</span>]
 Copying configuration: [<span style="color:#f92672">-</span>]
 Copying configuration: []
 Copying configuration: [<span style="color:#f92672">|</span>]
 Copying configuration: [<span style="color:#f92672">/</span>]
 Copying configuration: [<span style="color:#f92672">-</span>]
 Copying configuration: [Success]
 ArubaCX(config<span style="color:#f92672">-</span>loopback<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span>)<span style="color:#75715e"># end</span>
 ArubaCX<span style="color:#75715e">#</span>
 Configuration to be loaded on vEOS:
 ip routing
 router ospf <span style="color:#ae81ff">1</span>
     router<span style="color:#f92672">-</span>id <span style="color:#ae81ff">2.2.2.2</span>
     passive<span style="color:#f92672">-</span>interface default
     no passive<span style="color:#f92672">-</span>interface Ethernet1
 interface Ethernet1
     no switchport
     no shutdown
     ip address <span style="color:#ae81ff">10.0.0.2</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
     ip ospf area <span style="color:#ae81ff">0.0.0.0</span>
 interface Loopback0
     ip address <span style="color:#ae81ff">192.168.2.1</span><span style="color:#f92672">/</span><span style="color:#ae81ff">24</span>
     ip ospf area <span style="color:#ae81ff">0.0.0.1</span>
 do wr
 Logged into vEOS<span style="color:#75715e">#, woohoo!</span>
 configure terminal
 vEOS(config)<span style="color:#75715e">#ip routing</span>
 vEOS(config)<span style="color:#75715e">#router ospf 1</span>
 vEOS(config<span style="color:#f92672">-</span>router<span style="color:#f92672">-</span>ospf)<span style="color:#75715e">#    router-id 2.2.2.2</span>
 vEOS(config<span style="color:#f92672">-</span>router<span style="color:#f92672">-</span>ospf)<span style="color:#75715e">#    passive-interface default</span>
 vEOS(config<span style="color:#f92672">-</span>router<span style="color:#f92672">-</span>ospf)<span style="color:#75715e">#    no passive-interface Ethernet1</span>
 vEOS(config<span style="color:#f92672">-</span>router<span style="color:#f92672">-</span>ospf)<span style="color:#75715e">#interface Ethernet1</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Et1)<span style="color:#75715e">#    no switchport</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Et1)<span style="color:#75715e">#    no shutdown</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Et1)<span style="color:#75715e">#    ip address 10.0.0.2/24</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Et1)<span style="color:#75715e">#    ip ospf area 0.0.0.0</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Et1)<span style="color:#75715e">#interface Loopback0</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Lo0)<span style="color:#75715e">#    ip address 192.168.2.1/24</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Lo0)<span style="color:#75715e">#    ip ospf area 0.0.0.1</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Lo0)<span style="color:#75715e">#do wr</span>
 Copy completed successfully<span style="color:#f92672">.</span>
 vEOS(config<span style="color:#f92672">-</span><span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>Lo0)<span style="color:#75715e">#end</span>
 vEOS<span style="color:#75715e">#</span>
</code></pre></div><h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you for reading this far. I really do appreciate it. Stay safe and cheers!</p>
<ul>
<li><a href="https://www.pluralsight.com/authors/nick-russo">Courses by Nick Russo</a></li>
<li><a href="https://github.com/JulioPDX/multi-vendor-python">GitHub Repo</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Aruba Spine Leaf Deployment With OSPFv3 and Link Local Addresses</title>
            <link>https://juliopdx.com/2021/03/29/aruba-spine-leaf-deployment-with-ospfv3-and-link-local-addresses/</link>
            <pubDate>Mon, 29 Mar 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/03/29/aruba-spine-leaf-deployment-with-ospfv3-and-link-local-addresses/</guid>
            <description>Introduction I was recently on my way to finishing IPv6 Fundamentals by Rick Graziani. I will admit I’m not the fastest reader! In the book Rick mentions the following:
 You could configure router R2’s interfaces with only link-local addresses, no global unicast addresses. This is because R2 has no end user interfaces. RFC 7404, Using Only Link-Local Addressing inside an IPv6 Network, discusses implementing routing protocols using only link-local addresses on infrastructure links.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/leaf_spine_link_local.png" alt="Link Local"></p>
<h2 id="introduction">Introduction</h2>
<p>I was recently on my way to finishing IPv6 Fundamentals by Rick Graziani. I will admit I’m not the fastest reader! In the book Rick mentions the following:</p>
<blockquote>
<p>You could configure router R2’s interfaces with only link-local addresses, no global unicast addresses. This is because R2 has no end user interfaces. RFC 7404, Using Only Link-Local Addressing inside an IPv6 Network, discusses implementing routing protocols using only link-local addresses on infrastructure links.</p>
</blockquote>
<p>In my previous post about deploying an Aruba spine leaf, I mentioned the possibility of wasting IP space. In IPv6, we do have an abundance of IP space, but this is still something the operator would have to maintain and work into their workflow or automation. Once I read this, I immediately looked up <a href="https://datatracker.ietf.org/doc/html/rfc7404">RFC 7404</a> “Using Only Link-Local Addressing inside an IPv6 Network”. The RFC does a really great job of breaking down the pros and cons. In this post we’ll be going over the pros. If want to learn more, feel free to check out the RFC.</p>
<p>Here is a list of a few of the advantages this provides. We’ll be going over most of these in this post.</p>
<ul>
<li>Simple address management (automation)</li>
<li>Lower configuration complexity (automation and reduced errors)</li>
<li>Smaller routing tables</li>
<li>Reduced attack surface (less routed links)</li>
</ul>
<h2 id="simple-address-management">Simple Address Management</h2>
<p>If you recall from the previous post, we had to assign the IPv6 equivalent of a point to point address on each spine to leaf connections. This would increase the amount of IP addresses used as well as burden the operator to maintain these configurations. When assigning a link local address on a router, we can essentially assign the same address to each interface. One note as well, I mentioned that Aruba did not support IP unnumbered in the last post. If you are assigning the same address to each interface, we are essentially arriving at the same objective. Below is a sample configuration of a leaf and spine router. Please be aware that we are still configuring a global unicast address (GUA) on a loopback for these devices. This can also be used for device connections (SSH) or handling traffic to NMS.</p>
<p><code>Spine01 Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">interface 1/1/1
     no shutdown
     description link to leaf01
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
 interface 1/1/2
     no shutdown
     description link to leaf02
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
 interface 1/1/3
     no shutdown
     description link to leaf03
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
 interface 1/1/4
     no shutdown
     description link to leaf04
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
 interface loopback 0
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 address 2001:db8:cafe:ffff::1/128
     ipv6 ospfv3 1 area 0.0.0.0
 !
 router ospfv3 1
     router-id 1.1.1.1
     passive-interface default
     area 0.0.0.0
</code></pre></div><p>Only the loopback 0 interface on spine01 has a GUA. Essentially, every interface connected to the leaf nodes has the exact same configuration. Easy win for our automation folks!</p>
<p><code>Leaf01 Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">lan 1,10
 interface mgmt
     no shutdown
     ip dhcp
 interface 1/1/1
     no shutdown
     description link to spine01
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
 interface 1/1/2
     no shutdown
     description link to spine02
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
 interface 1/1/6
     no shutdown
     description link to Linux1
     no routing
     vlan access 10
 interface loopback 0
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 address 2001:db8:cafe:fd00::1/128
     ipv6 ospfv3 1 area 0.0.0.0
 interface vlan 10
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 address 2001:db8:cafe:a::1/64
     no ipv6 nd suppress-ra
     ipv6 ospfv3 1 area 0.0.0.0
 !
 router ospfv3 1
     router-id 10.0.0.1
     passive-interface default
     area 0.0.0.0
</code></pre></div><p>Difference in leaf nodes are the port VLAN assignments and VLAN interfaces.</p>
<h2 id="lower-configuration-complexity">Lower Configuration Complexity</h2>
<p>My main background on automation is around Ansible. I will provide some basic YAML variable file as an idea on how the configuration has been simplified. I’ll use spine01 for this example.</p>
<p><code>Spine01 variables with GUA addresses</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">spine01</span>
<span style="color:#f92672">link_local</span>: <span style="color:#ae81ff">fe80:face:cafe::1/64</span>
<span style="color:#f92672">interfaces</span>:
  <span style="color:#f92672">1/1/1</span>:
    <span style="color:#f92672">ipv6_address</span>: <span style="color:#ae81ff">2001</span>:<span style="color:#ae81ff">db8:cafe:fe01::a/127</span>
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf01</span>
  <span style="color:#f92672">1/1/2</span>:
    <span style="color:#f92672">ipv6_address</span>: <span style="color:#ae81ff">2001</span>:<span style="color:#ae81ff">db8:cafe:fe02::a/127</span>
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf02</span>
  <span style="color:#f92672">1/1/3</span>:
    <span style="color:#f92672">ipv6_address</span>: <span style="color:#ae81ff">2001</span>:<span style="color:#ae81ff">db8:cafe:fe03::a/127</span>
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf03</span>
  <span style="color:#f92672">1/1/4</span>:
    <span style="color:#f92672">ipv6_address</span>: <span style="color:#ae81ff">2001</span>:<span style="color:#ae81ff">db8:cafe:fe04::a/127</span>
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf04</span>
  <span style="color:#f92672">loopback0</span>:
    <span style="color:#f92672">ipv6_address</span>: <span style="color:#ae81ff">2001</span>:<span style="color:#ae81ff">db8:cafe:ffff::1/128</span>
</code></pre></div><p><code>Spine01 variables with only link-local</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">spine01</span>
<span style="color:#f92672">link_local</span>: <span style="color:#ae81ff">fe80:face:cafe::1/64</span>
<span style="color:#f92672">interfaces</span>:
  <span style="color:#f92672">1/1/1</span>:
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf01</span>
  <span style="color:#f92672">1/1/2</span>:
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf02</span>
  <span style="color:#f92672">1/1/3</span>:
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf03</span>
  <span style="color:#f92672">1/1/4</span>:
    <span style="color:#f92672">description</span>: <span style="color:#ae81ff">link to leaf04</span>
  <span style="color:#f92672">loopback0</span>:
    <span style="color:#f92672">ipv6_address</span>: <span style="color:#ae81ff">2001</span>:<span style="color:#ae81ff">db8:cafe:ffff::1/128</span>
</code></pre></div><p>The example is basic in nature but you can see how mistakes can be reduced and configuration can be simplified.</p>
<h2 id="smaller-routing-tables">Smaller Routing Tables</h2>
<p>Since we are using link local addresses, they have a scope that is… local to the link. The routing protocols will not propagate this as reachable networks, this in turn will reduce the total size of our routing tables. This has a great benefit of saving memory and speeding up convergence times. For comparison, check out the routing table on leaf01 when using GUA addresses and only using link local.</p>
<p><code>Using GUA /127 addresses</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">leaf01# show ipv6 ospfv3 routes | b 2001
  2001:db8:cafe:a::/64 (i) area:0.0.0.0
       directly attached to interface vlan10, cost 100 distance 110
  2001:db8:cafe:14::/64 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
  2001:db8:cafe:14::/64 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
  2001:db8:cafe:1e::/64 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
  2001:db8:cafe:1e::/64 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
  2001:db8:cafe:28::/64 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
  2001:db8:cafe:28::/64 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
  2001:db8:cafe:fd00::2/128 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
  2001:db8:cafe:fd00::2/128 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
  2001:db8:cafe:fd00::3/128 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
  2001:db8:cafe:fd00::3/128 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
  2001:db8:cafe:fd00::4/128 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
  2001:db8:cafe:fd00::4/128 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
  2001:db8:cafe:fe01::a/127 (i) area:0.0.0.0
       directly attached to interface 1/1/1, cost 100 distance 110
  2001:db8:cafe:fe02::a/127 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
  2001:db8:cafe:fe03::a/127 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
  2001:db8:cafe:fe04::a/127 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
  2001:db8:cafe:ff01::a/127 (i) area:0.0.0.0
       directly attached to interface 1/1/2, cost 100 distance 110
  2001:db8:cafe:ff02::a/127 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
  2001:db8:cafe:ff03::a/127 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
  2001:db8:cafe:ff04::a/127 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
  2001:db8:cafe:ffff::1/128 (i) area:0.0.0.0
       via fe80:face:cafe::1 interface 1/1/1, cost 100 distance 110
  2001:db8:cafe:ffff::2/128 (i) area:0.0.0.0
       via fe80:face:cafe::2 interface 1/1/2, cost 100 distance 110
</code></pre></div><p><code>Only link local addresses</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">leaf01#   show ipv6 ospfv3 routes | b 2001
 2001:db8:cafe:a::/64 (i) area:0.0.0.0
      directly attached to interface vlan10, cost 100 distance 110
 2001:db8:cafe:14::/64 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
 2001:db8:cafe:14::/64 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
 2001:db8:cafe:1e::/64 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
 2001:db8:cafe:1e::/64 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
 2001:db8:cafe:28::/64 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
 2001:db8:cafe:28::/64 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
 2001:db8:cafe:fd00::2/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fd00::2/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:fd00::3/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fd00::3/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:fd00::4/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fd00::4/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:ffff::1/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 100 distance 110
 2001:db8:cafe:ffff::2/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 100 distance 110
</code></pre></div><p><code>Trace from leaf01 and linux04</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">leaf01# traceroute6 2001:db8:cafe:28:5200:ff:fe0a:0
 traceroute to 2001:db8:cafe:28:5200:ff:fe0a:0 (2001:db8:cafe:28:5200:ff:fe0a:0) from 2001:db8:cafe:a::1, 30 hops max, 3 sec. timeout, 3 probes, 24 byte packets
  1  2001:db8:cafe:ffff::1 (2001:db8:cafe:ffff::1)  4.712 ms  70.151 ms  24.438 ms
  2  2001:db8:cafe:28::1 (2001:db8:cafe:28::1)  18.437 ms  58.055 ms  17.812 ms
  3  2001:db8:cafe:28:5200:ff:fe0a:0 (2001:db8:cafe:28:5200:ff:fe0a:0)  12.547 ms  53.539 ms  17.951 ms
 leaf01#
</code></pre></div><p>Last little bit, if you notice the output of the trace, the first hop is the GUA of loopback0 on spine01</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>Thank you for reading this far, I really do appreciate it. If you want to learn more on using link-local addresses between router links, check out <a href="https://datatracker.ietf.org/doc/html/rfc7404">RFC 7404</a>! Take care and stay safe! Cheers!</p>
<ul>
<li>Previous post: <a href="https://juliopdx.com/2021/03/18/aruba-spine-leaf-with-ospfv3-and-ipv6/">Aruba Spine Leaf with OSPFv3 and IPv6</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Aruba Spine Leaf With OSPFv3 and IPv6</title>
            <link>https://juliopdx.com/2021/03/18/aruba-spine-leaf-with-ospfv3-and-ipv6/</link>
            <pubDate>Thu, 18 Mar 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/03/18/aruba-spine-leaf-with-ospfv3-and-ipv6/</guid>
            <description>Introduction Hello and thank you for checking out this post! In this post I hope to breakdown the topology you see above. I’ll walk through the design, IPv6 IP allocations, and OSPFv3. I have a very small background in IPv6, basically enough to get past a few Cisco exams, which inevitably gets forgotten about after some time of little to no use. I purchased IPv6 Fundamentals by Rick Graziani, I want to say almost a year ago.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/aruba_spine_leaf.png" alt="Aruba Spine Leaf"></p>
<h2 id="introduction">Introduction</h2>
<p>Hello and thank you for checking out this post! In this post I hope to breakdown the topology you see above. I’ll walk through the design, IPv6 IP allocations, and OSPFv3. I have a very small background in IPv6, basically enough to get past a few Cisco exams, which inevitably gets forgotten about after some time of little to no use. I purchased IPv6 Fundamentals by Rick Graziani, I want to say almost a year ago. Its been sitting there haunting me for months.</p>
<p>I finally cracked it open and I can say it is incredible in getting you used to IPv6 and understanding the technologies involved. I’m about a third of the way done with the book and I hope to put some of what I’ve learned to practice in the topology above and in this post. Also, maybe helping folks learn a bit about spine leaf deployments and IPv6 in general.</p>
<p>Since its just you and me here. Lets pretend our site was assigned the 2001:db8:cafe::/48 global unicast prefix. Let me tell you, this gives us a whole bunch of IP space. I mean unimaginable amounts of IP space. This prefix will be more important as we go through the post. Please note, this design is fairly basic in nature and doesn’t use any fancy stuff like EVPN/VXLAN/LAG.</p>
<h2 id="design">Design</h2>
<p>The design you see above is using Aruba CX nodes with version <code>10.06.0001</code> running on EVE-NG. The base of the topology is a spine leaf design. In a spine leaf design, every spine connects to every leaf. Every leaf connects to every spine. Spines do not connect with each other. In some situations, leaf switches can connect together. For example, when dual connecting a host. In the case of this topology we have two spines and four leaf nodes. The Linux nodes you see below are just running Linux Slax. They will be used for some lightweight testing and verification of connectivity.</p>
<p>Lets knock out a few of the simple things. I’ll be using OSPFv3 in this example. The nodes were required to have a router ID set. In this case I stuck with spine01 having 1.1.1.1, spine02 having 1.1.1.2, and so on. For the leaf nodes I used the 10.0.0.x pattern. X being the number of the leaf.</p>
<p>The leaf to spine connections just follow a pattern, port 1 on each leaf connects to spine01 and port 2 on each leaf connects to spine02. Speaking of the leaf to spine connections, you will notice that these are point to point connections. In the IPv4 world this would usually mean we have a /30 or /31 network on the link. IPv6 has something similar, the /127 network.</p>
<p>We will use a portion of the highest network in 2001:db8:cafe::/48. Connections to spine01 will be on 2001:db8:cafe:fe::/56 and connections to spine02 will be on the 2001:db8:cafe:ff::/56 network. You will probably notice the pattern I followed in the diagram. Leaf nodes will always get the ::b address and spines will be assigned the ::a address.</p>
<p>At this point you might be thinking, wow this is wasting a lot of IP space. Remember, with IPv6 you have an incredible amount of IP space. This doesn’t mean you shouldn’t plan ahead but also don’t spend months on this. One small caveat, I’m not saying this is a great or even good IP plan for this deployment. Just going by what I’ve learned so far and putting it to use. Below you’ll see the link configurations on spine01. The output for spine02 is essentially the same.</p>
<h2 id="ospfv3-and-spine-nodes">OSPFv3 and Spine Nodes</h2>
<p><code>Spine01 Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">!
interface mgmt
     no shutdown
     ip dhcp
interface 1/1/1
     no shutdown
     description link to leaf01
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 address 2001:db8:cafe:fe01::a/127
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
interface 1/1/2
     no shutdown
     description link to leaf02
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 address 2001:db8:cafe:fe02::a/127
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
interface 1/1/3
     no shutdown
     description link to leaf03
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 address 2001:db8:cafe:fe03::a/127
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
interface 1/1/4
     no shutdown
     description link to leaf04
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 address 2001:db8:cafe:fe04::a/127
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
interface loopback 0
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 address 2001:db8:cafe:ffff::1/128
     ipv6 ospfv3 1 area 0.0.0.0
!
router ospfv3 1
     router-id 1.1.1.1
     passive-interface default
     area 0.0.0.0
!
</code></pre></div><p>A lot to unpack there right? Ill do my best to break it down below. Lets just focus on the small snippet below.</p>
<p><code>Interface Snippet</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">interface 1/1/4
     no shutdown
     description link to leaf04
     ipv6 address link-local fe80:face:cafe::1/64
     ipv6 address 2001:db8:cafe:fe04::a/127
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
!
router ospfv3 1
     router-id 1.1.1.1
     passive-interface default
     area 0.0.0.0
</code></pre></div><p>The link local address comes with every IPv6 enabled interface. Whether its Windows, Mac, or whatever. Doesn’t matter. You can leave this alone and the device will generate a link local address on its own. In our world, its better to just configure the link local address ourselves. This helps when working with routing protocols and figuring out what the source of traffic may be. You can configure the same link local address on each interface of a device. Link local addresses start with fe80, and as you can see, I’ve assigned the same throughout each interface.</p>
<p>Not much to add on the global unicast address (2001:). This is following the plan mentioned above. Note that all spine01 GUA end in ::a. “ipv6 ospfv3 1 area 0.0.0.0”, just enabling OSPF under the interface. One good thing to note, if you noticed there is “no ipv6 ospfv3 passive” under each point to point interface. By default we are setting each OSPF enabled interface to passive.</p>
<p>This is a good way of limiting the amount of chatter in a OSPF network. This will also skip some OSPF states since no DR/BDR elections occur or even need to occur on point to point links. Under the main OSPFv3 configuration; we are setting every interface to passive by default, setting router ID, and activating area 0. Bidirectional forwarding detection (BFD) is enabled under each point to point interface to help with failure detection on links.</p>
<p>I want to take a quick second and mention IP unnumbered. This does seem like a lot of IP addresses to manage, even if we are following a pattern. To my knowledge and some google searches, I don’t believe Aruba supports this feature. IP unnumbered essentially lets you borrow an IP address from an interface. For example, borrowing the loopback 0 address in our deployment and using it on all point to point links. If your network operating system supports this feature, use it! I think that’s enough for the spines, lets take a look at one of the leaf nodes. Below is leaf01, again, all other leaf nodes are essentially the same.</p>
<h2 id="ospfv3-and-leaf-nodes">OSPFv3 and Leaf Nodes</h2>
<p><code>Leaf01 Configuration</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">vlan 1,10
interface 1/1/1
     no shutdown
     description link to spine01
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 address 2001:db8:cafe:fe01::b/127
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
interface 1/1/2
     no shutdown
     description link to spine02
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 address 2001:db8:cafe:ff01::b/127
     ipv6 ospfv3 1 area 0.0.0.0
     no ipv6 ospfv3 passive
     ipv6 ospfv3 network point-to-point
     ipv6 ospfv3 bfd
interface 1/1/6
     no shutdown
     description link to Linux1
     no routing
     vlan access 10
interface loopback 0
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 address 2001:db8:cafe:fd00::1/128
     ipv6 ospfv3 1 area 0.0.0.0
interface vlan 10
     ipv6 address link-local fe80:beef:cafe::1/64
     ipv6 address 2001:db8:cafe:a::1/64
     no ipv6 nd suppress-ra
     ipv6 ospfv3 1 area 0.0.0.0
!
router ospfv3 1
     router-id 10.0.0.1
     passive-interface default
     area 0.0.0.0
</code></pre></div><p>Looks fairly similar right? Lets just focus on the new stuff. I added VLAN 10 on leaf01, VLAN 20 on leaf02, and so on. After that I just assigned the host interface to the appropriate VLAN. Here is where things get fun. In hex you get the range of 1-15. A-F make up what we know as 10-15. Remember our whole network is 2001:db8:cafe::/48. I decided to use the next available prefix that matched the VLAN number.</p>
<p>For example if the VLAN is 10, that would give us 2001:db8:cafe:a::/64. If the VLAN was 20, that would give us 2001:db8:cafe:14::/64. Looking at that last IP, the 1 in 14 is in the 16&rsquo;s place, so that adds up to 16, and the 4 is in the 1&rsquo;s place. 16 + 4 = 20, or VLAN 20. Now you might be wondering, why are you using a /64. Well, the powers that be recommend all LAN networks be a /64. This helps nodes acquire addresses automatically without a DHCP server. More on that in a sec. The /64 would give you over 18,000,000,000,000,000,000 possible Interface IDs…. on one LAN… wow.</p>
<h2 id="hosts">Hosts</h2>
<p>You may have wondered, what does the “no ipv6 nd suppress-ra” command do? This is a great time to talk about hosts or end devices. Usually these systems will have static addresses for management and tracking. I left the Linux hosts as default so they could use the built in processes with IPv6 to acquire an address. Back to the /64 prefix size. We have just split the 128 bit address in half. 64 are for the prefix/subnet and what’s left are for hosts, called an interface ID in IPv6.</p>
<p>In IPv6 hosts can use a process called SLAAC (stateless address autoconfiguration). I wont go deep into details but essentially the host sends router solicitation (RS) messages and the router responds with router advertisement (RA) messages. The host uses the information in the RA message, with the prefix to determine how to go about getting an interface ID.</p>
<p>In our case the host will use EUI-64. Another new thing! This ones not so bad. Essentially the host uses its 48 bit MAC address, splits it in half to insert FFFE in the middle, and then flips the seventh bit to make it a zero or a one. Don’t worry if its over your head. Just know the host uses its MAC to build the interface ID and inserts FFFE in the middle!</p>
<p>Back to our “no ipv6 nd suppress-ra” command. From my testing it seems that Aruba CX devices wont send the RA message needed by the hosts by default. In this case, just add that command and away you go. I&rsquo;ll finish the technical stuff with a snap of Wireshark between Linux1 and leaf01</p>
<p><code>Packet capture between leaf01 and linux01</code></p>
<p><img src="/blog/images/aruba_ipv6_wireshark.png" alt="IPv6 Packets"></p>
<p><code>Packet Information</code></p>
<table>
<thead>
<tr>
<th style="text-align:center">Packet #</th>
<th style="text-align:center">Type</th>
<th style="text-align:center">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">8</td>
<td style="text-align:center">Router Solicitation (RS)</td>
<td style="text-align:center">Linux1 using its link local address (fe80:5200:ff:fe07:0) to reach the All-routers address (ff02::2)</td>
</tr>
<tr>
<td style="text-align:center">9</td>
<td style="text-align:center">Router Advertisement (RA)</td>
<td style="text-align:center">leaf01 using its link local address (fe80:beef:cafe::1) to reach the All-nodes address (ff02::1)</td>
</tr>
</tbody>
</table>
<p>Check out the ICMPv6 options sent in the RA message. The big one to note here is the prefix information of 2001:db8:cafe:a::/64, the VLAN 10 prefix information on leaf01. Just a note, the RA message is also the only way a node can get a default gateway in IPv6. Whether its SLAAC or DHCPv6, the default gateway comes from the RA message.</p>
<p>What’s a good network engineering article without some routes and pings!</p>
<p><code>OSPFv3 Routes at leaf01</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">leaf01# show ipv6 ospfv3 routes | b 2001
 2001:db8:cafe:a::/64 (i) area:0.0.0.0
      directly attached to interface vlan10, cost 100 distance 110
 2001:db8:cafe:14::/64 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
 2001:db8:cafe:14::/64 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
 2001:db8:cafe:1e::/64 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
 2001:db8:cafe:1e::/64 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
 2001:db8:cafe:28::/64 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 300 distance 110
 2001:db8:cafe:28::/64 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 300 distance 110
 2001:db8:cafe:fd00::2/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fd00::2/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:fd00::3/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fd00::3/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:fd00::4/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fd00::4/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:fe01::a/127 (i) area:0.0.0.0
      directly attached to interface 1/1/1, cost 100 distance 110
 2001:db8:cafe:fe02::a/127 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fe03::a/127 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:fe04::a/127 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 200 distance 110
 2001:db8:cafe:ff01::a/127 (i) area:0.0.0.0
      directly attached to interface 1/1/2, cost 100 distance 110
 2001:db8:cafe:ff02::a/127 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:ff03::a/127 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:ff04::a/127 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 200 distance 110
 2001:db8:cafe:ffff::1/128 (i) area:0.0.0.0
      via fe80:face:cafe::1 interface 1/1/1, cost 100 distance 110
 2001:db8:cafe:ffff::2/128 (i) area:0.0.0.0
      via fe80:face:cafe::2 interface 1/1/2, cost 100 distance 110
</code></pre></div><p><code>Trace from leaf01 to VLAN 40 at leaf04</code></p>
<p><img src="/blog/images/ping6_linux1_linux4.png" alt="Pings"></p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>Thank you so much for reading this far! I am just getting my bearings with IPv6 but it really is great. Maybe as I work through the book I&rsquo;ll add some border leaf nodes and get some NAT64 going! Check out the links below if curious about learning more on IPv6 or data center networking. Too many RFCs to list but you can easily google them. For example RFC 6164, “Using 127-Bit IPv6 Prefixes on Inter-Router Links”.</p>
<ul>
<li><a href="https://www.amazon.com/Cloud-Native-Data-Center-Networking/dp/1492045608">Cloud Native Data Center Networking: Architecture, Protocols, and Tools - I cant find the free link!</a></li>
<li><a href="https://www.amazon.com/IPv6-Fundamentals-Straightforward-Approach-Understanding/dp/1587144778/ref=sr_1_2?dchild=1&amp;keywords=ipv6+fundamentals&amp;qid=1616115794&amp;sr=8-2">IPv6 Fundamentals: A Straightforward Approach to Understanding IPv6 Second Edition</a></li>
<li>FYI, these are not sponsored links at all. I earn nothing if you buy or don’t buy a product.</li>
<li>Follow up post: <a href="https://juliopdx.com/2021/03/29/aruba-spine-leaf-deployment-with-ospfv3-and-link-local-addresses/">Using only link-local addresses between routers</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>My Journey to Completing the David Goggins 4x4x48 Challenge</title>
            <link>https://juliopdx.com/2021/03/08/my-journey-to-completing-the-david-goggins-4x4x48-challenge/</link>
            <pubDate>Mon, 08 Mar 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/03/08/my-journey-to-completing-the-david-goggins-4x4x48-challenge/</guid>
            <description>Introduction Hello and thank you for tuning in. Just for clarity, the 4x4x48 challenge was created by David Goggins. The challenge requires participants to run 4 miles every 4 hours for 48 hours. Participants are allowed to substitute running with walking or general exercise. The main goal is to get people moving as well as donate to charity!
The next thought may be why the hell would someone want to run 48 miles on a perfectly nice weekend?</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Hello and thank you for tuning in. Just for clarity, the 4x4x48 challenge was created by David Goggins. The challenge requires participants to run 4 miles every 4 hours for 48 hours. Participants are allowed to substitute running with walking or general exercise. The main goal is to get people moving as well as donate to charity!</p>
<p>The next thought may be why the hell would someone want to run 48 miles on a perfectly nice weekend? Everyone will have their own reasons, heck for some this may just be a training run, shout-out to all the ultra runners out there (I am not one). I had a few reasons for attempting this challenge. Most of them are pretty selfish of me to be honest. This was a physical test for a distance I’ve never even come close to reaching. Even greater than the physical challenge was the mental challenge I would eventually be faced with. This impacted me mentally way more than physically when it was all done with.</p>
<p>I think this is a good spot to give you all a bit of a background on my running history. I have an okay athletic background. Never been the fastest, strongest, or lowest weight. Heck before this challenge I was sitting right at 235 lbs. My time in the military only required running 1.5 miles under 13 minutes or so to get a decent physical score. I’ve competed with teams on endurance races, but even then I was responsible for maybe one or two legs of the eventual 70 plus miles. Where each leg I may be responsible for 3-5 miles. For this event I wasn’t just the leg, I was the team… yikes!</p>
<p>Since my time in the military I still run 3-4 times a week for about 3-5 miles each run. Last year I had the idea of training for a marathon. It would be my first and I thought it would be a sweet goal. I was up to 16 miles in one session, which I thought was great. Inevitably, the current pandemic we are all going through put a stop to all races. I figured I’d keep training and eventually I could get one marathon under my belt. Well we are all human and around August/September of last year, I dealt with a nagging upper calf injury that wouldn’t go away. I did some PT and took a lot of time off (months). Injuries aren’t new to me, hell I’ve torn the ACL in my left knee 3 times! At least that injury puts you all the way down for some time.</p>
<h2 id="run-preparation">Run Preparation</h2>
<p>That was a really long winded background but its my blog so its all good! Now for how I prepared for this challenge. Full disclosure, I am no running expert and this is definitely not the go to plan or even a good plan. Its just what worked for me. I continued running 3-4 times a week for about 3-5 miles after I recovered from the calf injury. I also added weight training using those fancy adjustable dumbbells, yay for home gyms! I eventually started running right after doing a leg day workout. Trying to trick my mind into dealing with the soreness I would face during this challenge. Oh boy I couldn’t be more wrong.</p>
<h2 id="running-technique">Running Technique</h2>
<p>The running technique I use is pretty damn simple. I run for a few minutes… then walk for 30 seconds. This technique was developed or popularized by Jeff Galloway. I heard about Jeff from a fellow co worker and picked up his book to train for the marathon, “Marathon, You Can Do It!”. The thought behind this technique is that you run for a certain amount of time and then walk. You basically do this in intervals and repeat. The small walk breaks in between running lowers the muscle breakdown and soreness experienced from constant running. Runners have even accomplished personal bests using this technique. Some folks may put you down because you aren’t constantly running, but take my advice and tell them to FUCK right off.</p>
<h2 id="nutrition">Nutrition</h2>
<p>Sorry for getting so spicy there at the end. While running the challenge, when I hit mile 30+ and other weekend runners are doing their thing and passing you, don’t get down. Just tell yourself “IM ON MILE FUCKING 30!”. Just a bit on nutrition, again I’m no expert. I basically searched the internets, what are the best fruits to eat after runs? I saw bananas (cramps) / oranges (muscle recovery). Besides the usual protein source to fuel muscles and a whole lot of carbs. I had some chicken and rice as my main meals. Pictured below is my race set up (thanks to my lovely wife).</p>
<p><img src="/blog/images/run_nutrition.jpg" alt="Food"></p>
<h2 id="pre-race">Pre Race</h2>
<p>So now its the night before the race. I tried for a while to place it in the back of my mind. Thinking about that many miles in general makes me nervous and boy was I nervous the night before. My advice would be to lay out all your clothes, gear, food, and chargers in one room or general area. It makes transitioning from relaxing to running so much easier. In my case I left all chargers connected so I just dropped off running watch or earbuds on their charger when a leg was done. Don’t forget this! My running light ran out of juice during one run and damn it was dark!</p>
<p>I did some simple math and figured we would all be running 12 total legs to complete the challenge. Initially I wanted to break down this write up leg by leg and honestly I think its better if I group them into thirds. Since the pain and tiredness you experience is similar between them.</p>
<h2 id="legs-1-4">Legs 1-4</h2>
<p>Some of the fastest legs here for obvious reason haha. I mentioned before I reached 16 miles in one running session. In my mind I knew I could at the very least make it to mile 16, which would take me to the end of leg 4. These runs were overall pretty fun and the muscle soreness and fatigue was low.</p>
<p><img src="/blog/images/legs_1_4.png" alt="1-4"></p>
<h2 id="midnight-runs">Midnight Runs</h2>
<p>I have to mention this. Being a two day event, you will be running in the dark. I would recommend getting a reflective vest and a light for the front and back. Stay safe out there folks and protect yourselves. The night runs for me were truly special. The world is so silent but loud at the same time. You hear everything; the wind moving grass, stop signs shaking, small critters, the sound of your breath, or the college kid puking his guts out over the porch. I’m not hating, enjoy yourself young man!</p>
<h2 id="legs-5-8">Legs 5-8</h2>
<p>These legs were honestly a total surprise to me. It would be mileage I have never reached before and that might be what fueled me during these runs. My overall times were pretty good. I maintained a good pace and muscle soreness around the quad, hip, and calf areas just started. Remember when I said “IM ON MILE FUCKING 30!”</p>
<p><img src="/blog/images/legs_5_8.png" alt="5-8"></p>
<h2 id="legs-9-12-go-get-it">Legs 9-12 &ldquo;Go get it&rdquo;</h2>
<p>I wont lie to anyone. Unless you are some experienced distance runner, these legs will hurt! I say that for a good reason. No one is making us do this. You can stop at anytime. Whatever the reason is that you chose to do this, I’m so happy and proud of you. It’ll hurt to even think of leaving that warm comfy bed at 4 AM. Just know that you made it this far and you should be so proud. “One more to go!! Go get it!!” – John Spiegel</p>
<p><img src="/blog/images/legs_9_12.png" alt="9-12"></p>
<p>Some of you may have wondered, if you have to run 4 miles every four hours, why the hell is this dude running 4.4? Again, for selfish reasons. I’ve never gone for this many miles. This got me thinking, “if I can make it that far, how much will I need to run to hit 52.4”. The equivalent of TWO marathons. From never running one to running two in 48 hours. I thought that would be incredible and pretty damn special.</p>
<h2 id="in-between-time">In Between Time</h2>
<p>What do you do in between each leg? I developed a pretty normal routine. I ate half a banana after each leg and some orange slices. I would then hydrate with plain water and get a meal in at normal times of the day. I stretched and foam rolled after each and every run, no exceptions. Even if you have to stretch in bed at 1 AM… do it! Besides that I just played some video games and read messages from awesome supporters.</p>
<h2 id="support">Support</h2>
<p>When embarking on great challenges, having a support team or person is critical. Thank you to everyone who like, comment, and subscribed… wait a minute this isn’t YouTube. Seriously, thank you to everyone that supported me and left a positive message. Thank you to all the support teams out there, y’all rock! Last but definitely not least. Big thanks to my wife Amanda, without her support I don’t know what I would be doing. Definitely not running 52 miles…</p>
<h2 id="you-can-do-it">You Can Do It</h2>
<p>This experience has showed me if you put your mind to it, you really can do anything. Cliché I know. Take me as a small example. After the third ACL tear, I could’ve given up on all athletic hopes and dreams. Maybe now I can claim the title of being the greatest to ever run 52 miles with a twitter handle of Julio_PDX…</p>
<p><img src="/blog/images/night_run.jpg" alt="Night Run"></p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://unsplash.com/photos/D68ADLeMh5Q">Featured Image by Denys Nevozhai</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Network Validation With Nornir &amp; Napalm</title>
            <link>https://juliopdx.com/2021/02/27/network-validation-with-nornir-napalm/</link>
            <pubDate>Sat, 27 Feb 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/02/27/network-validation-with-nornir-napalm/</guid>
            <description>Intro I wanted to get my feet wet with Nornir/NAPALM and Python network automation in general. One simple goal to start with in network automation is validating configurations or deploying “show commands” and returning some kind of useful information. I wanted to start with something very simple, as easy wins make me want to keep progressing. In this post I will break down how I validate SNMP information on network devices using Nornir and NAPALM.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/nornir_logo_02.jpg" alt="Nornir Image"></p>
<h2 id="intro">Intro</h2>
<p>I wanted to get my feet wet with <a href="https://nornir.readthedocs.io/en/latest/">Nornir</a>/<a href="https://napalm.readthedocs.io/en/latest/">NAPALM</a> and Python network automation in general. One simple goal to start with in network automation is validating configurations or deploying “show commands” and returning some kind of useful information. I wanted to start with something very simple, as easy wins make me want to keep progressing. In this post I will break down how I validate SNMP information on network devices using Nornir and NAPALM.</p>
<p>I think its worth knowing a bit of background to set the stage for future network engineers that are curious about diving into python for network automation. For starters, you can do it! I started my career with zero knowledge about python or programming in general. My initial and current automation efforts revolve mostly around using <a href="https://docs.ansible.com/ansible/latest/network/index.html">Ansible</a>. I think both tools are great and they each have their place in this space. In the bottom of this post I’ll include some links on resources I’ve used.</p>
<p>I wont go too deep into explaining Nornir and NAPALM but I’ll do my best. Nornir is a framework in which, it can provide a foundation for plugins or tools to be constructed around it. Think of Ansible but pure Python. NAPALM acts almost like a translator for multivendor environments. For example, if our goal is to get interface information or SNMP information from a device, we don’t care what commands are executed. We just need the data returned to use in a constructed manner.</p>
<p>NAPALM does this in a concept it calls “getters”. Getters are basically functions in NAPALM that interact with networking devices and return structured information. For a list of all supported vendors and getters, please see <a href="https://napalm.readthedocs.io/en/latest/support/">here</a>.</p>
<p><code>Current Structure</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-virtual-2004:~/git/nornir_snmp_validation$ tree -I venv
 .
 ├── config.yaml
 ├── inventory
 │   ├── defaults.yaml
 │   ├── groups.yaml
 │   └── hosts.yaml
 ├── nornir.log
 ├── README.md
 ├── requirements.txt
 ├── snmp_validate.py
 └── validate
     └── cisco.yaml
 <span style="color:#ae81ff">2</span> directories, <span style="color:#ae81ff">9</span> files
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e"># Creating python environment</span>
python3 -m venv venv

<span style="color:#75715e"># Activating virtual environment</span>
source venv/bin/activate

<span style="color:#75715e"># Clone git repo</span>
git clone https://github.com/JulioPDX/nornir_snmp_validation.git
cd nornir_snmp_validation/

<span style="color:#75715e"># Install requirements</span>
pip install -r requirements.txt
</code></pre></div><p>Nornir was recently updated to split some of the functionality from Nornir core and the additional plugins. I just went ahead and installed pretty much all of the third party plugins at once. <a href="https://nornir.tech/nornir/plugins/">Here is a link to those plugins</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"> nornir<span style="color:#f92672">==</span>3.0.0
 nornir-ansible<span style="color:#f92672">==</span>2020.9.26
 nornir-jinja2<span style="color:#f92672">==</span>0.1.1
 nornir-napalm<span style="color:#f92672">==</span>0.1.1
 nornir-netbox<span style="color:#f92672">==</span>0.2.0
 nornir-netmiko<span style="color:#f92672">==</span>0.1.1
 nornir-pyez<span style="color:#f92672">==</span>0.0.10
 nornir-scrapli<span style="color:#f92672">==</span>2021.1.30
 nornir-utils<span style="color:#f92672">==</span>0.1.1
</code></pre></div><p>lets take a look at the config.yaml file. If you are coming from an Ansible background, just think about this as the ansible.cfg file. This allows you to set the inventory plugin, runner (# of ssh sessions and serial vs threaded), and locations for certain files that we’ll talk about in a bit.</p>
<p><code>config.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
 <span style="color:#f92672">inventory</span>:
   <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">SimpleInventory</span>
   <span style="color:#f92672">options</span>:
     <span style="color:#f92672">host_file</span>: <span style="color:#e6db74">&#34;inventory/hosts.yaml&#34;</span>
     <span style="color:#f92672">group_file</span>: <span style="color:#e6db74">&#34;inventory/groups.yaml&#34;</span>
     <span style="color:#f92672">defaults_file</span>: <span style="color:#e6db74">&#34;inventory/defaults.yaml&#34;</span>
 <span style="color:#f92672">runner</span>:
   <span style="color:#f92672">plugin</span>: <span style="color:#ae81ff">threaded</span>
   <span style="color:#f92672">options</span>:
     <span style="color:#f92672">num_workers</span>: <span style="color:#ae81ff">10</span>
</code></pre></div><p>Now lets go into our inventory folder. You will notice that we have three files; hosts.yaml (for host data), group.yaml (group data), and defaults.yaml (defaults/global data). I found that Nornir is really flexible on where you can store information. I’ll show you what made sense to me.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
 <span style="color:#f92672">port</span>: <span style="color:#ae81ff">22</span>
 <span style="color:#f92672">username</span>: <span style="color:#ae81ff">cisco</span>
 <span style="color:#f92672">password</span>: <span style="color:#ae81ff">cisco</span>
 <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">ios</span>
<span style="color:#75715e"># Since I am using only Cisco devices in this lab, I just set the</span>
<span style="color:#75715e"># login information at the defaults level.</span>
<span style="color:#75715e"># In multivendor deployments, you would probably break this out</span>
<span style="color:#75715e"># into the groups.yaml file or under each hosts...</span>
</code></pre></div><p>At the moment I didn’t do much of anything with the goups.yaml file. In my testing it seems that if a group is called out in the hosts.yaml file, it must exist in the groups.yaml file.</p>
<p><code>groups.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">cisco</span>:
   <span style="color:#f92672">data</span>:
 <span style="color:#f92672">switches</span>:
   <span style="color:#f92672">data</span>:
 <span style="color:#f92672">routers</span>:
   <span style="color:#f92672">data</span>:
</code></pre></div><p>I tried to keep the host.yaml file as simple as possible. I created some fake data under each host to signify what site they belong to. This was mainly as an example from the official documentation. I assigned different hosts to different groups depending on if they were switches or routers, and what vendor. Oh, going back to the group variables. If different systems had different connection parameters, you could put that under the groups.yaml file and just assign the host to that group.</p>
<p><code>hosts.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
<span style="color:#f92672">NY-SW-01</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">192.168.10.37</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">cisco</span>
    - <span style="color:#ae81ff">switches</span>
  <span style="color:#f92672">data</span>:
    <span style="color:#f92672">site</span>: <span style="color:#ae81ff">NY</span>
<span style="color:#f92672">NE-RTR-01</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">192.168.10.38</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">cisco</span>
    - <span style="color:#ae81ff">routers</span>
  <span style="color:#f92672">data</span>:
    <span style="color:#f92672">site</span>: <span style="color:#ae81ff">NE</span>
<span style="color:#f92672">OR-SW-01</span>:
  <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">192.168.10.36</span>
  <span style="color:#f92672">groups</span>:
    - <span style="color:#ae81ff">cisco</span>
    - <span style="color:#ae81ff">switches</span>
  <span style="color:#f92672">data</span>:
    <span style="color:#f92672">site</span>: <span style="color:#ae81ff">OR</span>
</code></pre></div><p>I’m going to side step a bit here and get into NAPALM. NAPALM has a lot of great plugin tasks that work with Nornir but the two that I used in this test were napalm_get and napalm_validate. The reason I used the get task? I needed to see how the information was returned by NAPALM so that I could then build the source file to validate against. A seasoned professional could probably just go straight to the docs, but I needed a bit more. The following NAPALM docs helped in creating this. <a href="https://napalm.readthedocs.io/en/develop/validate/index.html">Napalm Validate</a>.</p>
<p>Below is a view of the /validate/cisco.yaml file. This was created from the link mentioned above and running the napalm_get task. Notice that the cisco.yaml file lists the name of the getter at the top or root of the file. You could lists multiple getters to validate multiple pieces of information, as long as its supported with your device.</p>
<p><code>/validate/cisco.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
- <span style="color:#f92672">get_snmp_information</span>:
    <span style="color:#f92672">community</span>:
      <span style="color:#f92672">myreadonly</span>:
        <span style="color:#f92672">acl</span>: <span style="color:#66d9ef">N</span><span style="color:#ae81ff">/A</span>
        <span style="color:#f92672">mode</span>: <span style="color:#ae81ff">ro</span>
      <span style="color:#f92672">mysecurestring</span>:
        <span style="color:#f92672">acl</span>: <span style="color:#66d9ef">N</span><span style="color:#ae81ff">/A</span>
        <span style="color:#f92672">mode</span>: <span style="color:#ae81ff">rw</span>
    <span style="color:#f92672">contact</span>: <span style="color:#ae81ff">JulioPDX</span>
    <span style="color:#f92672">location</span>: <span style="color:#ae81ff">mylocation</span>
</code></pre></div><p>Here is a snippet from each devices SNMP configuration. Looking at the validate file above and the snippets, can you guess which device will fail?</p>
<p><code>SNMP Configurations</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">NY-SW-01<span style="color:#f92672">(</span>config<span style="color:#f92672">)</span><span style="color:#75715e">#do show run | inc snmp</span>
 snmp-server community mysecurestring RW
 snmp-server community myreadonly RO
 snmp-server location mylocation
 snmp-server contact JulioPDX

NE-RTR-01<span style="color:#f92672">(</span>config<span style="color:#f92672">)</span><span style="color:#75715e">#do show run | inc snmp</span>
 snmp-server community public RO
 snmp-server community private RW
 snmp-server location mylocation
 snmp-server contact JulioPDX

OR-SW-01<span style="color:#f92672">(</span>config<span style="color:#f92672">)</span><span style="color:#75715e">#do show run | inc snmp</span>
 snmp-server community myreadonly RO
 snmp-server community mysecurestring RW
 snmp-server location mylocation
 snmp-server contact JulioPDX
</code></pre></div><p><code>snmp_validate.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e"># Import required modules</span>
<span style="color:#f92672">from</span> nornir <span style="color:#f92672">import</span> InitNornir
<span style="color:#f92672">from</span> nornir_utils.plugins.functions <span style="color:#f92672">import</span> print_result
<span style="color:#f92672">from</span> nornir_napalm.plugins.tasks <span style="color:#f92672">import</span> napalm_get, napalm_validate

<span style="color:#75715e"># Initializing Nornir</span>
nr <span style="color:#f92672">=</span> InitNornir(config_file<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;config.yaml&#34;</span>)

get_snmp <span style="color:#f92672">=</span> nr<span style="color:#f92672">.</span>run(name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;GATHERING SNMP&#34;</span>, task<span style="color:#f92672">=</span>napalm_get, getters<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;get_snmp_information&#34;</span>])

print_result(get_snmp)

validate <span style="color:#f92672">=</span> nr<span style="color:#f92672">.</span>run(name<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;VALIDATE SNMP&#34;</span>, task<span style="color:#f92672">=</span>napalm_validate, src<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;./validate/cisco.yaml&#34;</span>)

print_result(validate)
</code></pre></div><p>Here is the output of the script, main parts to look at are towards the bottom when the snmp validations are printed to the screen.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-virtual-2004:~/git/nornir_snmp_validation$ python snmp_validate.py
 GATHERING SNMP**
 NE-RTR-01 ** changed : False *
 vvvv GATHERING SNMP ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
 <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;get_snmp_information&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;chassis_id&#39;</span>: <span style="color:#e6db74">&#39;&#39;</span>,
                         <span style="color:#e6db74">&#39;community&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;private&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;acl&#39;</span>: <span style="color:#e6db74">&#39;N/A&#39;</span>,
                                                     <span style="color:#e6db74">&#39;mode&#39;</span>: <span style="color:#e6db74">&#39;rw&#39;</span><span style="color:#f92672">}</span>,
                                        <span style="color:#e6db74">&#39;public&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;acl&#39;</span>: <span style="color:#e6db74">&#39;N/A&#39;</span>,
                                                    <span style="color:#e6db74">&#39;mode&#39;</span>: <span style="color:#e6db74">&#39;ro&#39;</span><span style="color:#f92672">}}</span>,
                         <span style="color:#e6db74">&#39;contact&#39;</span>: <span style="color:#e6db74">&#39;JulioPDX&#39;</span>,
                         <span style="color:#e6db74">&#39;location&#39;</span>: <span style="color:#e6db74">&#39;mylocation&#39;</span><span style="color:#f92672">}}</span>
 ^^^^ END GATHERING SNMP ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 NY-SW-01 ** changed : False
 vvvv GATHERING SNMP ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
 <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;get_snmp_information&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;chassis_id&#39;</span>: <span style="color:#e6db74">&#39;9NA76XNH9Z8&#39;</span>,
                         <span style="color:#e6db74">&#39;community&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;myreadonly&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;acl&#39;</span>: <span style="color:#e6db74">&#39;N/A&#39;</span>,
                                                        <span style="color:#e6db74">&#39;mode&#39;</span>: <span style="color:#e6db74">&#39;ro&#39;</span><span style="color:#f92672">}</span>,
                                        <span style="color:#e6db74">&#39;mysecurestring&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;acl&#39;</span>: <span style="color:#e6db74">&#39;N/A&#39;</span>,
                                                            <span style="color:#e6db74">&#39;mode&#39;</span>: <span style="color:#e6db74">&#39;rw&#39;</span><span style="color:#f92672">}}</span>,
                         <span style="color:#e6db74">&#39;contact&#39;</span>: <span style="color:#e6db74">&#39;JulioPDX&#39;</span>,
                         <span style="color:#e6db74">&#39;location&#39;</span>: <span style="color:#e6db74">&#39;mylocation&#39;</span><span style="color:#f92672">}}</span>
 ^^^^ END GATHERING SNMP ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 OR-SW-01 ** changed : False
 vvvv GATHERING SNMP ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
 <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;get_snmp_information&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;chassis_id&#39;</span>: <span style="color:#e6db74">&#39;9QA913OVFJS&#39;</span>,
                         <span style="color:#e6db74">&#39;community&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;myreadonly&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;acl&#39;</span>: <span style="color:#e6db74">&#39;N/A&#39;</span>,
                                                        <span style="color:#e6db74">&#39;mode&#39;</span>: <span style="color:#e6db74">&#39;ro&#39;</span><span style="color:#f92672">}</span>,
                                        <span style="color:#e6db74">&#39;mysecurestring&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;acl&#39;</span>: <span style="color:#e6db74">&#39;N/A&#39;</span>,
                                                            <span style="color:#e6db74">&#39;mode&#39;</span>: <span style="color:#e6db74">&#39;rw&#39;</span><span style="color:#f92672">}}</span>,
                         <span style="color:#e6db74">&#39;contact&#39;</span>: <span style="color:#e6db74">&#39;JulioPDX&#39;</span>,
                         <span style="color:#e6db74">&#39;location&#39;</span>: <span style="color:#e6db74">&#39;mylocation&#39;</span><span style="color:#f92672">}}</span>
 ^^^^ END GATHERING SNMP ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 VALIDATE SNMP***
 NE-RTR-01 ** changed : False *
 vvvv VALIDATE SNMP ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
 <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: False,
 <span style="color:#e6db74">&#39;get_snmp_information&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: False,
                         <span style="color:#e6db74">&#39;extra&#39;</span>: <span style="color:#f92672">[]</span>,
                         <span style="color:#e6db74">&#39;missing&#39;</span>: <span style="color:#f92672">[]</span>,
                         <span style="color:#e6db74">&#39;present&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;community&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: False,
                                                     <span style="color:#e6db74">&#39;diff&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: False,
                                                               <span style="color:#e6db74">&#39;extra&#39;</span>: <span style="color:#f92672">[]</span>,
                                                               <span style="color:#e6db74">&#39;missing&#39;</span>: <span style="color:#f92672">[</span> <span style="color:#e6db74">&#39;myreadonly&#39;</span>,
                                                                            <span style="color:#e6db74">&#39;mysecurestring&#39;</span><span style="color:#f92672">]</span>,
                                                               <span style="color:#e6db74">&#39;present&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#f92672">}}</span>,
                                                     <span style="color:#e6db74">&#39;nested&#39;</span>: True<span style="color:#f92672">}</span>,
                                      <span style="color:#e6db74">&#39;contact&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                   <span style="color:#e6db74">&#39;nested&#39;</span>: False<span style="color:#f92672">}</span>,
                                      <span style="color:#e6db74">&#39;location&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                    <span style="color:#e6db74">&#39;nested&#39;</span>: False<span style="color:#f92672">}}}</span>,
 <span style="color:#e6db74">&#39;skipped&#39;</span>: <span style="color:#f92672">[]}</span>
 ^^^^ END VALIDATE SNMP ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 NY-SW-01 ** changed : False
 vvvv VALIDATE SNMP ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
 <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
 <span style="color:#e6db74">&#39;get_snmp_information&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                         <span style="color:#e6db74">&#39;extra&#39;</span>: <span style="color:#f92672">[]</span>,
                         <span style="color:#e6db74">&#39;missing&#39;</span>: <span style="color:#f92672">[]</span>,
                         <span style="color:#e6db74">&#39;present&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;community&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                     <span style="color:#e6db74">&#39;nested&#39;</span>: True<span style="color:#f92672">}</span>,
                                      <span style="color:#e6db74">&#39;contact&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                   <span style="color:#e6db74">&#39;nested&#39;</span>: False<span style="color:#f92672">}</span>,
                                      <span style="color:#e6db74">&#39;location&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                    <span style="color:#e6db74">&#39;nested&#39;</span>: False<span style="color:#f92672">}}}</span>,
 <span style="color:#e6db74">&#39;skipped&#39;</span>: <span style="color:#f92672">[]}</span>
 ^^^^ END VALIDATE SNMP ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 OR-SW-01 ** changed : False
 vvvv VALIDATE SNMP ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
 <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
 <span style="color:#e6db74">&#39;get_snmp_information&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                         <span style="color:#e6db74">&#39;extra&#39;</span>: <span style="color:#f92672">[]</span>,
                         <span style="color:#e6db74">&#39;missing&#39;</span>: <span style="color:#f92672">[]</span>,
                         <span style="color:#e6db74">&#39;present&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;community&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                     <span style="color:#e6db74">&#39;nested&#39;</span>: True<span style="color:#f92672">}</span>,
                                      <span style="color:#e6db74">&#39;contact&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                   <span style="color:#e6db74">&#39;nested&#39;</span>: False<span style="color:#f92672">}</span>,
                                      <span style="color:#e6db74">&#39;location&#39;</span>: <span style="color:#f92672">{</span> <span style="color:#e6db74">&#39;complies&#39;</span>: True,
                                                    <span style="color:#e6db74">&#39;nested&#39;</span>: False<span style="color:#f92672">}}}</span>,
 <span style="color:#e6db74">&#39;skipped&#39;</span>: <span style="color:#f92672">[]}</span>
 ^^^^ END VALIDATE SNMP ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 <span style="color:#f92672">(</span>venv<span style="color:#f92672">)</span> juliopdx@juliopdx-virtual-2004:~/git/nornir_snmp_validation$
</code></pre></div><p>Did you catch which one failed? If you guessed NE-RTR-01 earlier you would be correct. If you look at the output for the NE router, there is a key for “complies” and it is set to False. So now we know one device in our three device environment did not pass our SNMP validation check. This could then be tied to kick off another task to fix SNMP, report to monitoring, or some other workflow. All up to the imagination.</p>
<h2 id="outro-and-links">Outro and Links</h2>
<p>I hope this post has shown you how NAPALM and Nornir can be leveraged to validate device configurations and dive into a bit of network automation with Python. Not too bad right? Below you’ll find some links to resources I have used and a link to the git repo. There really are so many great resources in this space for network engineers. I cant possibly list them all. Thank you for reading this far, it is much appreciated! Stay safe out there.</p>
<ul>
<li><a href="https://github.com/JulioPDX/nornir_snmp_validation">Git Repository</a></li>
<li><a href="https://nostarch.com/pythoncrashcourse2e">Python Crash Course Second Edition</a></li>
<li><a href="https://www.packtpub.com/product/mastering-python-networking-third-edition/9781839214677">Mastering Python Networking</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLKZjLeG8AwtES8apo6gonH4XmN_9xIo8W">Chuck Black 52 Weeks of Python</a></li>
<li><a href="https://github.com/networktocode/awesome-network-automation">NTC Awesome List</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Creating List of IP Addresses With Ansible Filter Plugin</title>
            <link>https://juliopdx.com/2021/02/02/creating-list-of-ip-addresses-with-ansible-filter-plugin/</link>
            <pubDate>Tue, 02 Feb 2021 00:00:00 +0000</pubDate>
            
            <guid>https://juliopdx.com/2021/02/02/creating-list-of-ip-addresses-with-ansible-filter-plugin/</guid>
            <description>Intro I recently had a use case where I needed a list of IP addresses from a prefix. My first step was to go through Ansible documentation on current filters to see if something would match my need. Long story short, there wasn’t. At least, nothing I could find which honestly is probably a huge possibility, and in that case this post is pointless. Ansible does have IP address filters but I noticed most all of the examples assume you already have a list of IP addresses to pass to the filters.</description>
            <content type="html"><![CDATA[<p><img src="/blog/images/router_pool_large.png" alt="Router Image"></p>
<h2 id="intro">Intro</h2>
<p>I recently had a use case where I needed a list of IP addresses from a prefix. My first step was to go through Ansible documentation on current filters to see if something would match my need. Long story short, there wasn’t. At least, nothing I could find which honestly is probably a huge possibility, and in that case this post is pointless. Ansible does have IP address filters but I noticed most all of the examples assume you already have a list of IP addresses to pass to the filters.</p>
<p>I should probably explain what a filter does just in case the reader isn’t aware. Filters allow you to transform data from something to something else. The simplest example being, turn this lower case string to upper case. See example below.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Filter Test</span>
  <span style="color:#f92672">connection</span>: <span style="color:#ae81ff">local</span>
  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">localhost</span>
  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">no</span>
  <span style="color:#f92672">vars</span>:
    <span style="color:#f92672">my_variable</span>: <span style="color:#ae81ff">mylowercasestring</span>

  <span style="color:#f92672">tasks</span>:
    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Turning lower case string to UPPER CASE</span>
      <span style="color:#f92672">debug</span>:
        <span style="color:#f92672">msg</span>: <span style="color:#e6db74">&#34;{{ my_variable | upper }}&#34;</span>

...
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">PLAY <span style="color:#f92672">[</span>Filter Test<span style="color:#f92672">]</span>
 TASK <span style="color:#f92672">[</span>Turning lower <span style="color:#66d9ef">case</span> string to UPPER CASE<span style="color:#f92672">]</span>
 ok: <span style="color:#f92672">[</span>localhost<span style="color:#f92672">]</span> <span style="color:#f92672">=</span>&gt; <span style="color:#f92672">{</span>
     <span style="color:#e6db74">&#34;msg&#34;</span>: <span style="color:#e6db74">&#34;MYLOWERCASESTRING&#34;</span>
 <span style="color:#f92672">}</span>
</code></pre></div><p>As you can see, filters are pretty neat ways to transform data. So there I was trying to create a list of IP addresses from a prefix, with no end in sight, and Google-fu failing me. I’m sure there is some whacky ways to make it happen, like a jinja template and reading a file or using some kind of range/with sequence option. In the end I came up with the following python script. This can be placed in the /filter_plugins directory where your playbook is stored.</p>
<p><code>ip_list.py</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#75715e">#!/usr/bin/python</span>

<span style="color:#f92672">from</span> netaddr <span style="color:#f92672">import</span> IPNetwork

<span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ip_stuff</span>(prefix):
    ips <span style="color:#f92672">=</span> []

    <span style="color:#66d9ef">for</span> ip <span style="color:#f92672">in</span> IPNetwork(prefix):
        ips<span style="color:#f92672">.</span>append(str(ip))
    <span style="color:#66d9ef">return</span> ips

<span style="color:#66d9ef">class</span> <span style="color:#a6e22e">FilterModule</span>(object):
    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">filters</span>(self):
        <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#39;ip_list&#39;</span>: ip_stuff}
</code></pre></div><p>Breaking down the code, we first import one library, I know some folks aren’t a fan of this on filters but oh well. Then we create an empty list called ips, we then trigger a for loop to each individual entry under the prefix that will be passed in. Each entry will be added to the ips list. After that is all done we return the final list. Shout out to the oddbit blog for giving me a place to start on creating this filter.</p>
<p><code>ip_list_test.yaml</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">---
- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">IP List Test</span>
  <span style="color:#f92672">connection</span>: <span style="color:#ae81ff">local</span>
  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">localhost</span>
  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">no</span>
  <span style="color:#f92672">vars</span>:
    <span style="color:#f92672">my_prefix</span>: <span style="color:#ae81ff">10.10.10.0</span><span style="color:#ae81ff">/29</span>

  <span style="color:#f92672">tasks</span>:
    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Setting a fact for my list of IP addresses</span>
      <span style="color:#f92672">set_fact</span>:
        <span style="color:#f92672">my_ip_addresses</span>: <span style="color:#e6db74">&#34;{{ my_prefix | ip_list }}&#34;</span>

    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Print list of IP addresses</span>
      <span style="color:#f92672">debug</span>:
        <span style="color:#f92672">msg</span>: <span style="color:#e6db74">&#34;{{ my_ip_addresses }}&#34;</span>

...
</code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">PLAY <span style="color:#f92672">[</span>IP List Test<span style="color:#f92672">]</span> *
 TASK <span style="color:#f92672">[</span>Setting a fact <span style="color:#66d9ef">for</span> my list of IP addresses<span style="color:#f92672">]</span> ***
 ok: <span style="color:#f92672">[</span>localhost<span style="color:#f92672">]</span>
 TASK <span style="color:#f92672">[</span>Print list of IP addresses<span style="color:#f92672">]</span> *
 ok: <span style="color:#f92672">[</span>localhost<span style="color:#f92672">]</span> <span style="color:#f92672">=</span>&gt; <span style="color:#f92672">{</span>
     <span style="color:#e6db74">&#34;msg&#34;</span>: <span style="color:#f92672">[</span>
         <span style="color:#e6db74">&#34;10.10.10.0&#34;</span>,
         <span style="color:#e6db74">&#34;10.10.10.1&#34;</span>,
         <span style="color:#e6db74">&#34;10.10.10.2&#34;</span>,
         <span style="color:#e6db74">&#34;10.10.10.3&#34;</span>,
         <span style="color:#e6db74">&#34;10.10.10.4&#34;</span>,
         <span style="color:#e6db74">&#34;10.10.10.5&#34;</span>,
         <span style="color:#e6db74">&#34;10.10.10.6&#34;</span>,
         <span style="color:#e6db74">&#34;10.10.10.7&#34;</span>
     <span style="color:#f92672">]</span>
 <span style="color:#f92672">}</span>
</code></pre></div><p>That’s about it. I hope you all enjoyed this write up and now you can make a list of IP addresses from a prefix… woohoo! Thank you for reading this far.</p>
]]></content>
        </item>
        
    </channel>
</rss>
