GMRS is pretty cheap and easy

I got myself a GMRS license 2 years ago. They are $80 for 5 years, and they allow an entire "immediate" family to use it. That's spouse, children, parents, in-laws. I thought I was going to put play around with some GMRS repeater/text data modes, never did.

Flash forward to more recent. Picked up a 4-pack of Baofeng BF-888S radios. These go for about $13 apiece and $42 for a 4-pack. They're 2-watt radios, and channelized (16 channels). Program with a computer, dead simple for others. Gave one to my wife's sister who lives 2 miles down the road. She can talk crystal clear to my son from inside her house. I drove around town with one able to talk to my son as well.

Adding a frequency list to the back is good if you want to talk to someone else.

FRS is limited to specific radios and 1/2 watt. These BF-888S radios are NOT FRS compliant. T GMRS allows 5W on FRS shared frequencies and 50 watts on dedicated. Amateur radio requires a control operator to be physically near the radio. With today's cheap radios, you can get high powered FRS radios, or use MURS frequencies, or find some off the books frequencies; There are lots of space between the FRS channels, and there are some old airplane to ground cellular frequencies that have been phased out. No one is monitoring and even at 5W, you're not going to bother anyone enough to draw enforcement. However, GMRS is probably the cheapest and easiest way to get long range legal communications going for a family (or small business). In my case if you consider 4 people will be using it, it comes to $4/user/year. We could even put a repeater on the roof, or some higher powered vehicle antennas if desired. We probably won't, because the whole goal is to replace my son's (now dead) walkie talkies with something that really works. The fact that he can talk to his Aunt down the road is a big bonus.

ansible flush handlers

In ansible playbooks, handlers (such as to restart a service) normally happen at the end of a run.
If you need ansible to run a handler between two tasks, there is "flush_handlers".

  - name: flush handlers
    meta: flush_handlers

Serial port console

This is how to get serial port console working on a Ubuntu 16.04 (or any systemd based OS) and how to access it with idrac/ssh.

To get serial port working on a running system:

systemctl enable [email protected]
systemctl start [email protected]

Update Grub:

To get serial console during boot up, including the grub menu:

Go ahead and edit /etc/default/grub

GRUB_CMDLINE_LINUX_DEFAULT="splash quiet"
GRUB_CMDLINE_LINUX="console=tty0"
GRUB_TERMINAL="console serial"
# also, it takes so long to boot a server, adding 10
# second to the grub menu is more good than harm
GRUB_TIMEOUT=10

Access it via idrac

ssh <idrac-ip> console com2

#yourtech-dailies

I browse pornhub for the articles. Not only is this an interesting article on the drop in traffic during the Hawaii Missile Alert, I also discovered that their insight's blog has a lot of good data analysis type articles. The blog itself is SFW with no bad images, though of course the logo and items mentioned in the articles themselves would not be. #yourtech-dailies

Mocking Consul for fun and profit.

I've been creating a fun microservice tool that provides a single API frontend and merges data from multiple backends. Since the app itself relies entirely on external data, I was wondering how in the world I would write unit tests for it. It's written in python using the amazing apistar framework. All of my external data so far is gathered using the requests library. The answer for this, turns out to requests-mock. Requests-mock will allow you create mock responses to requests.

The documentation is pretty straightforward, but I was having some trouble wrapping my head around how I would use it to test the code in my app. To start simple, I decided to mock consul, which is one of my datasources.

Get a value into consul

First off, let's go ahead and setup. Go to https://www.consul.io/ and download the consul binary for your OS.

  1. Start consul in dev mode/foreground: consul agent -dev
  2. Insert a key: consul kv put foo bar
  3. Let's request that key with curl. Be verbose, because there are some headers you'll want later.
$ curl http://127.0.0.1:8500/v1/kv/foo -v
* Connected to localhost (127.0.0.1) port 8500 (#0)
> GET /v1/kv/foo HTTP/1.1
> Host: localhost:8500
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< X-Consul-Index: 7
< X-Consul-Knownleader: true
< X-Consul-Lastcontact: 0
< Date: Thu, 04 Jan 2018 11:04:20 GMT
< Content-Length: 158
<
[
    {
        "LockIndex": 0,
        "Key": "foo",
        "Flags": 0,
        "Value": "YmFy",
        "CreateIndex": 7,
        "ModifyIndex": 7
    }
]

You may be wondering why "Value" is YmFy. That's because consul uses base64 encoding. Running echo YmFy | base64 -d will give you the string bar.

Read consul with python requests

Awesome, now I can write a fancy python script to show off my foo. Create a file called requestkey.py.

import base64
import json
import requests

URL = "http://127.0.0.1:8500/v1/kv"

def requestkey(key):
  url = "{}/{}".format(URL, key)
  r = requests.get(url)
  data = r.json()
  v = base64.b64decode(data[0]['Value'])
  # FYI v will return a byte instead of a string (b'foo'), we'll decode that to a string
  return (v.decode())

if __name__ == '__main__':
  v = requestkey('foo')
  print("Here is my foo: {}".format(v))

Run it:

$ python requestkey.py
FETCHED: foo RESULT: b'bar

Live unit test

Let's create a test.py file. This will ensure the value of foo is equal to bar.

import unittest
from requestkey import requestkey

class TestStringMethods(unittest.TestCase):

    def test_foo(self):
        v = requestkey('foo')
        self.assertEqual(v, 'bar')

if __name__ == '__main__':
    unittest.main()

And run it:

$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.011s

OK

This works great! But what if the consul server on the CICD server running my test.py has a different value for foo? Or no consul server at all?

(py3) jh1:mocktests ytjohn$ consul kv delete foo
Success! Deleted key: foo
(py3) jh1:mocktests ytjohn$ python test.py
$ consul kv put foo bard
Success! Data written to: foo
(py3) jh1:mocktests ytjohn$ python test.py
F
======================================================================
FAIL: test_foo (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 8, in test_foo
    self.assertEqual(v, 'bar')
AssertionError: 'bard' != 'bar'
- bard
?    -
+ bar


----------------------------------------------------------------------
Ran 1 test in 0.012s

FAILED (failures=1)

This is a problem. My code is still perfectly fine, but because the live data has changed, my test fails. That is what we hope to solve.

Let's mock consul.

As I alluded at the top of my post, I hope to solve this with requests-mock. There's some fancy things I see like registering URIs, but to start, I am just going use the Mocker example they have. It's a good thing I did a curl request earlier to see what the actual response will be.

import json
import requests_mock
import unittest

from requestkey import requestkey

class TestStringMethods(unittest.TestCase):

    def setUp(self):
       self.baseurl = 'http://127.0.0.1:8500/v1/kv'

    def test_foo(self):
        key = 'foo'
        url = '{}/{}'.format(self.baseurl, key)
        response = [{
                    "LockIndex": 0,
                    "Key": "foo",
                    "Flags": 0,
                    "Value": "YmFy",
                    "CreateIndex": 7,
                    "ModifyIndex": 7}]

        with requests_mock.Mocker() as m:
          m.get(url, text=json.dumps(response))
          v = requestkey('foo')
          self.assertEqual(v, 'bar')

if __name__ == '__main__':
    unittest.main()

Let's try our test:

(py3) jh1:mocktests ytjohn$ consul kv get foo
bard
(py3) jh1:mocktests ytjohn$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.010s

OK

This is great. I can develop locally, store working examples in my test code, and test against that.

Requests is fun, but what about python-consul?

The truth is, I don't talk to consul using requests and base 64 decoding. For some reason, I thought it would
be easier for you to follow along if I did straight requests. But in reality, most people are going to use python-consul. In fact, here is my getkey.py file doing just that.

import consul

def getkey(key):
  c = consul.Consul() # consul defaults to 127.0.0.1:8500
  index, data = c.kv.get(key, index=None)
  return data['Value']

if __name__ == '__main__':
  v = getkey('foo')
  print("Here is my foo: {}".format(v))

Now I'm going to rewrite my test.py to test both of these. I moved my response into the setUp
class, renamed test_foo to test_request and added a test_get to test my getkey function.

import json
import requests_mock
import unittest

from requestkey import requestkey
from getkey import getkey

class TestStringMethods(unittest.TestCase):

    def setUp(self):
       self.baseurl = 'http://127.0.0.1:8500/v1/kv'
       self.response_foo = [{
                    "LockIndex": 0,
                    "Key": "foo",
                    "Flags": 0,
                    "Value": "YmFy",
                    "CreateIndex": 7,
                    "ModifyIndex": 7}]

    def test_request(self):
        key = 'foo'
        url = '{}/{}'.format(self.baseurl, key)

        with requests_mock.Mocker() as m:
          m.get(url, text=json.dumps(self.response_foo))
          v = requestkey('foo')
          self.assertEqual(v, 'bar')

    def test_get(self):
        key = 'foo'
        url = '{}/{}'.format(self.baseurl, key)

        with requests_mock.Mocker() as m:
          m.get(url, text=json.dumps(self.response_foo))
          v = getkey('foo')
          self.assertEqual(v, 'bar')

if __name__ == '__main__':
    unittest.main()

Let's see how this does:

$ python test.py
E.
======================================================================
ERROR: test_get (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 35, in test_get
    v = getkey('foo')
  File "/Users/ytjohn/vsprojects/unsafe/mocktests/getkey.py", line 7, in getkey
    index, data = c.kv.get(key, index=None)
  File "/Users/ytjohn/.venvs/py3/lib/python3.6/site-packages/consul/base.py", line 538, in get
    params=params)
  File "/Users/ytjohn/.venvs/py3/lib/python3.6/site-packages/consul/std.py", line 22, in get
    self.session.get(uri, verify=self.verify, cert=self.cert)))
  File "/Users/ytjohn/.venvs/py3/lib/python3.6/site-packages/consul/base.py", line 227, in cb
    return response.headers['X-Consul-Index'], data
  File "/Users/ytjohn/.venvs/py3/lib/python3.6/site-packages/requests/structures.py", line 54, in __getitem__
    return self._store[key.lower()][1]
KeyError: 'x-consul-index'

----------------------------------------------------------------------
Ran 2 tests in 0.016s

FAILED (errors=1)

Oh what disaster! My fancy getkey code is failing tests!. What is x-consul-index anyways? Well, it looks
to be response.headers['X-Consul-Index'], which is a header we saw in the curl request. Fortunately,
mock allows you to provide headers as well.

  1. Add headers to setUp: self.headers = {'X-Consul-Index': "7"} (yes, value must be a string)
  2. Add the header response to your mockup: m.get(url, text=json.dumps(self.response_foo), headers=self.headers_foo)
$ python test.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.012s

OK

Outstanding. And for completion, here is the final test.py:

import json
import requests_mock
import unittest

from requestkey import requestkey
from getkey import getkey

class TestStringMethods(unittest.TestCase):

    def setUp(self):
       self.baseurl = 'http://127.0.0.1:8500/v1/kv'
       self.headers_foo = {'X-Consul-Index': "7"}
       self.response_foo = [{
                    "LockIndex": 0,
                    "Key": "foo",
                    "Flags": 0,
                    "Value": "YmFy",
                    "CreateIndex": 7,
                    "ModifyIndex": 7}]

    def test_request(self):
        key = 'foo'
        url = '{}/{}'.format(self.baseurl, key)

        with requests_mock.Mocker() as m:
          m.get(url, text=json.dumps(self.response_foo))
          v = requestkey('foo')
          self.assertEqual(v, 'bar')

    def test_get(self):
        key = 'foo'
        url = '{}/{}'.format(self.baseurl, key)

        with requests_mock.Mocker() as m:
          m.get(url, text=json.dumps(self.response_foo), headers=self.headers_foo)
          v = getkey('foo')
          self.assertEqual(v, 'bar')

if __name__ == '__main__':
    unittest.main()

What is the point?

There isn't much pointing to having a block of code produce a static value and then check to see if it is that value. However, when we start taking actions based on values (live, maintenance, true, false, -1), we can definitely check to see if our code behaves an expected way based on a collection of sample data we store. I can also check for how I handle incomplete data. A big part of my microservice correlates devices with network interfaces, ip addresses, and vlans. Not every interface has an ip, not every ip has a vlan. Not every network has a default gateway. I have to determine which ip is "primary". So as I collect examples of devices with different configurations, I should be able to register urls and responses for each device. If my code is expecting a vlan to be a number, but instead I receive a "None" - will I handle that or will I throw an exception error?

Looking forward, I can envision having sample json data stored with functions to provide the desired response and headers needed.