github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/repository/trusty/haproxy/hooks/tests/test_reverseproxy_hooks.py (about)

     1  import os
     2  import base64
     3  import yaml
     4  
     5  from testtools import TestCase
     6  from mock import patch, call
     7  
     8  import hooks
     9  
    10  
    11  class ReverseProxyRelationTest(TestCase):
    12  
    13      def setUp(self):
    14          super(ReverseProxyRelationTest, self).setUp()
    15  
    16          self.config_get = self.patch_hook("config_get")
    17          self.config_get.return_value = {"monitoring_port": "10000",
    18                                          "peering_mode": "active-passive"}
    19          self.relations_of_type = self.patch_hook("relations_of_type")
    20          self.get_config_services = self.patch_hook("get_config_services")
    21          self.log = self.patch_hook("log")
    22          self.write_service_config = self.patch_hook("write_service_config")
    23          self.apply_peer_config = self.patch_hook("apply_peer_config")
    24          self.apply_peer_config.side_effect = lambda value: value
    25  
    26      def patch_hook(self, hook_name):
    27          mock_controller = patch.object(hooks, hook_name)
    28          mock = mock_controller.start()
    29          self.addCleanup(mock_controller.stop)
    30          return mock
    31  
    32      def test_relation_data_returns_none(self):
    33          self.get_config_services.return_value = {
    34              "service": {
    35                  "service_name": "service",
    36                  },
    37              }
    38          self.relations_of_type.return_value = []
    39          self.assertIs(None, hooks.create_services())
    40          self.log.assert_called_once_with("No backend servers, exiting.")
    41          self.write_service_config.assert_not_called()
    42  
    43      def test_relation_data_returns_no_relations(self):
    44          self.get_config_services.return_value = {
    45              "service": {
    46                  "service_name": "service",
    47                  },
    48              }
    49          self.relations_of_type.return_value = []
    50          self.assertIs(None, hooks.create_services())
    51          self.log.assert_called_once_with("No backend servers, exiting.")
    52          self.write_service_config.assert_not_called()
    53  
    54      def test_relation_no_services(self):
    55          self.get_config_services.return_value = {}
    56          self.relations_of_type.return_value = [
    57              {"port": 4242,
    58               "__unit__": "foo/0",
    59               "hostname": "backend.1",
    60               "private-address": "1.2.3.4"},
    61          ]
    62          self.assertIs(None, hooks.create_services())
    63          self.log.assert_called_once_with("No services configured, exiting.")
    64          self.write_service_config.assert_not_called()
    65  
    66      def test_no_port_in_relation_data(self):
    67          self.get_config_services.return_value = {
    68              "service": {
    69                  "service_name": "service",
    70                  },
    71              }
    72          self.relations_of_type.return_value = [
    73              {"private-address": "1.2.3.4",
    74               "__unit__": "foo/0"},
    75          ]
    76          self.assertIs(None, hooks.create_services())
    77          self.log.assert_has_calls([call.log(
    78              "No port in relation data for 'foo/0', skipping.")])
    79          self.write_service_config.assert_not_called()
    80  
    81      def test_no_private_address_in_relation_data(self):
    82          self.get_config_services.return_value = {
    83              "service": {
    84                  "service_name": "service",
    85                  },
    86              }
    87          self.relations_of_type.return_value = [
    88              {"port": 4242,
    89               "__unit__": "foo/0"},
    90          ]
    91          self.assertIs(None, hooks.create_services())
    92          self.log.assert_has_calls([call.log(
    93              "No private-address in relation data for 'foo/0', skipping.")])
    94          self.write_service_config.assert_not_called()
    95  
    96      def test_relation_unknown_service(self):
    97          self.get_config_services.return_value = {
    98              "service": {
    99                  "service_name": "service",
   100                  },
   101              }
   102          self.relations_of_type.return_value = [
   103              {"port": 4242,
   104               "hostname": "backend.1",
   105               "service_name": "invalid",
   106               "private-address": "1.2.3.4",
   107               "__unit__": "foo/0"},
   108          ]
   109          self.assertIs(None, hooks.create_services())
   110          self.log.assert_has_calls([call.log(
   111              "Service 'invalid' does not exist.")])
   112          self.write_service_config.assert_not_called()
   113  
   114      def test_no_relation_but_has_servers_from_config(self):
   115          self.get_config_services.return_value = {
   116              None: {
   117                  "service_name": "service",
   118                  },
   119              "service": {
   120                  "service_name": "service",
   121                  "servers": [
   122                      ("legacy-backend", "1.2.3.1", 4242, ["maxconn 42"]),
   123                      ]
   124                  },
   125              }
   126          self.relations_of_type.return_value = []
   127  
   128          expected = {
   129              'service': {
   130                  'service_name': 'service',
   131                  'service_host': '0.0.0.0',
   132                  'service_port': 10002,
   133                  'servers': [
   134                      ("legacy-backend", "1.2.3.1", 4242, ["maxconn 42"]),
   135                      ],
   136                  },
   137              }
   138          self.assertEqual(expected, hooks.create_services())
   139          self.write_service_config.assert_called_with(expected)
   140  
   141      def test_relation_default_service(self):
   142          self.get_config_services.return_value = {
   143              None: {
   144                  "service_name": "service",
   145                  },
   146              "service": {
   147                  "service_name": "service",
   148                  },
   149              }
   150          self.relations_of_type.return_value = [
   151              {"port": 4242,
   152               "hostname": "backend.1",
   153               "private-address": "1.2.3.4",
   154               "__unit__": "foo/0"},
   155          ]
   156  
   157          expected = {
   158              'service': {
   159                  'service_name': 'service',
   160                  'service_host': '0.0.0.0',
   161                  'service_port': 10002,
   162                  'servers': [('foo-0-4242', '1.2.3.4', 4242, [])],
   163                  },
   164              }
   165          self.assertEqual(expected, hooks.create_services())
   166          self.write_service_config.assert_called_with(expected)
   167  
   168      def test_with_service_options(self):
   169          self.get_config_services.return_value = {
   170              None: {
   171                  "service_name": "service",
   172                  },
   173              "service": {
   174                  "service_name": "service",
   175                  "server_options": ["maxconn 4"],
   176                  },
   177              }
   178          self.relations_of_type.return_value = [
   179              {"port": 4242,
   180               "hostname": "backend.1",
   181               "private-address": "1.2.3.4",
   182               "__unit__": "foo/0"},
   183          ]
   184  
   185          expected = {
   186              'service': {
   187                  'service_name': 'service',
   188                  'service_host': '0.0.0.0',
   189                  'service_port': 10002,
   190                  'server_options': ["maxconn 4"],
   191                  'servers': [('foo-0-4242', '1.2.3.4',
   192                               4242, ["maxconn 4"])],
   193                  },
   194              }
   195          self.assertEqual(expected, hooks.create_services())
   196          self.write_service_config.assert_called_with(expected)
   197  
   198      def test_with_service_name(self):
   199          self.get_config_services.return_value = {
   200              None: {
   201                  "service_name": "service",
   202                  },
   203              "foo_service": {
   204                  "service_name": "foo_service",
   205                  "server_options": ["maxconn 4"],
   206                  },
   207              }
   208          self.relations_of_type.return_value = [
   209              {"port": 4242,
   210               "hostname": "backend.1",
   211               "service_name": "foo_service",
   212               "private-address": "1.2.3.4",
   213               "__unit__": "foo/0"},
   214          ]
   215  
   216          expected = {
   217              'foo_service': {
   218                  'service_name': 'foo_service',
   219                  'service_host': '0.0.0.0',
   220                  'service_port': 10002,
   221                  'server_options': ["maxconn 4"],
   222                  'servers': [('foo-0-4242', '1.2.3.4',
   223                               4242, ["maxconn 4"])],
   224                  },
   225              }
   226          self.assertEqual(expected, hooks.create_services())
   227          self.write_service_config.assert_called_with(expected)
   228  
   229      def test_no_service_name_unit_name_match_service_name(self):
   230          self.get_config_services.return_value = {
   231              None: {
   232                  "service_name": "foo_service",
   233                  },
   234              "foo_service": {
   235                  "service_name": "foo_service",
   236                  "server_options": ["maxconn 4"],
   237                  },
   238              }
   239          self.relations_of_type.return_value = [
   240              {"port": 4242,
   241               "hostname": "backend.1",
   242               "private-address": "1.2.3.4",
   243               "__unit__": "foo/1"},
   244          ]
   245  
   246          expected = {
   247              'foo_service': {
   248                  'service_name': 'foo_service',
   249                  'service_host': '0.0.0.0',
   250                  'service_port': 10002,
   251                  'server_options': ["maxconn 4"],
   252                  'servers': [('foo-1-4242', '1.2.3.4',
   253                               4242, ["maxconn 4"])],
   254                  },
   255              }
   256          self.assertEqual(expected, hooks.create_services())
   257          self.write_service_config.assert_called_with(expected)
   258  
   259      def test_with_sitenames_match_service_name(self):
   260          self.get_config_services.return_value = {
   261              None: {
   262                  "service_name": "service",
   263                  },
   264              "foo_srv": {
   265                  "service_name": "foo_srv",
   266                  "server_options": ["maxconn 4"],
   267                  },
   268              }
   269          self.relations_of_type.return_value = [
   270              {"port": 4242,
   271               "hostname": "backend.1",
   272               "sitenames": "foo_srv bar_srv",
   273               "private-address": "1.2.3.4",
   274               "__unit__": "foo/0"},
   275          ]
   276  
   277          expected = {
   278              'foo_srv': {
   279                  'service_name': 'foo_srv',
   280                  'service_host': '0.0.0.0',
   281                  'service_port': 10002,
   282                  'server_options': ["maxconn 4"],
   283                  'servers': [('foo-0-4242', '1.2.3.4',
   284                               4242, ["maxconn 4"])],
   285                  },
   286              }
   287          self.assertEqual(expected, hooks.create_services())
   288          self.write_service_config.assert_called_with(expected)
   289  
   290      def test_with_juju_services_match_service_name(self):
   291          self.get_config_services.return_value = {
   292              None: {
   293                  "service_name": "service",
   294                  },
   295              "foo_service": {
   296                  "service_name": "foo_service",
   297                  "server_options": ["maxconn 4"],
   298                  },
   299              }
   300          self.relations_of_type.return_value = [
   301              {"port": 4242,
   302               "hostname": "backend.1",
   303               "private-address": "1.2.3.4",
   304               "__unit__": "foo/1"},
   305          ]
   306  
   307          expected = {
   308              'foo_service': {
   309                  'service_name': 'foo_service',
   310                  'service_host': '0.0.0.0',
   311                  'service_port': 10002,
   312                  'server_options': ["maxconn 4"],
   313                  'servers': [('foo-1-4242', '1.2.3.4',
   314                               4242, ["maxconn 4"])],
   315                  },
   316              }
   317  
   318          result = hooks.create_services()
   319  
   320          self.assertEqual(expected, result)
   321          self.write_service_config.assert_called_with(expected)
   322  
   323      def test_with_sitenames_no_match_but_unit_name(self):
   324          self.get_config_services.return_value = {
   325              None: {
   326                  "service_name": "service",
   327                  },
   328              "foo": {
   329                  "service_name": "foo",
   330                  "server_options": ["maxconn 4"],
   331                  },
   332              }
   333          self.relations_of_type.return_value = [
   334              {"port": 4242,
   335               "hostname": "backend.1",
   336               "sitenames": "bar_service baz_service",
   337               "private-address": "1.2.3.4",
   338               "__unit__": "foo/0"},
   339          ]
   340  
   341          expected = {
   342              'foo': {
   343                  'service_name': 'foo',
   344                  'service_host': '0.0.0.0',
   345                  'service_port': 10002,
   346                  'server_options': ["maxconn 4"],
   347                  'servers': [('foo-0-4242', '1.2.3.4',
   348                               4242, ["maxconn 4"])],
   349                  },
   350              }
   351          self.assertEqual(expected, hooks.create_services())
   352          self.write_service_config.assert_called_with(expected)
   353  
   354      def test_with_multiple_units_in_relation(self):
   355          """
   356          Have multiple units specifying "services" in the relation.
   357          Make sure data is created correctly with create_services()
   358          """
   359          self.get_config_services.return_value = {
   360              None: {
   361                  "service_name": "service",
   362                  },
   363              }
   364          self.relations_of_type.return_value = [
   365              {"port": 4242,
   366               "private-address": "1.2.3.4",
   367               "__unit__": "foo/0",
   368               "services": yaml.safe_dump([{
   369                   "service_name": "service",
   370                   "servers": [('foo-0', '1.2.3.4',
   371                                4242, ["maxconn 4"])]
   372                   }])
   373               },
   374              {"port": 4242,
   375               "private-address": "1.2.3.5",
   376               "__unit__": "foo/1",
   377               "services": yaml.safe_dump([{
   378                   "service_name": "service",
   379                   "servers": [('foo-0', '1.2.3.5',
   380                                4242, ["maxconn 4"])]
   381                   }])
   382               },
   383          ]
   384  
   385          expected = {
   386              'service': {
   387                  'service_name': 'service',
   388                  'service_host': '0.0.0.0',
   389                  'service_port': 10002,
   390                  'servers': [
   391                      ['foo-0', '1.2.3.4', 4242, ["maxconn 4"]],
   392                      ['foo-0', '1.2.3.5', 4242, ["maxconn 4"]]
   393                      ]
   394                  },
   395              }
   396          self.assertEqual(expected, hooks.create_services())
   397          self.write_service_config.assert_called_with(expected)
   398  
   399      def test_with_multiple_units_and_backends_in_relation(self):
   400          """
   401          Have multiple units specifying "services" in the relation
   402          using the "backends" option. Make sure data is created correctly
   403          with create_services()
   404          """
   405          self.get_config_services.return_value = {
   406              None: {
   407                  "service_name": "service",
   408                  },
   409              }
   410          self.relations_of_type.return_value = [
   411              {"port": 4242,
   412               "private-address": "1.2.3.4",
   413               "__unit__": "foo/0",
   414               "services": yaml.safe_dump([{
   415                   "service_name": "service",
   416                   "servers": [('foo-0', '1.2.3.4',
   417                                4242, ["maxconn 4"])],
   418                   "backends": [
   419                       {"backend_name": "foo-bar",
   420                        "servers": [('foo-bar-0', '2.2.2.2',
   421                                     2222, ["maxconn 4"])],
   422                        },
   423                   ]
   424                   }])
   425               },
   426              {"port": 4242,
   427               "private-address": "1.2.3.5",
   428               "__unit__": "foo/1",
   429               "services": yaml.safe_dump([{
   430                   "service_name": "service",
   431                   "servers": [('foo-0', '1.2.3.5',
   432                                4242, ["maxconn 4"])],
   433                   "backends": [
   434                       {"backend_name": "foo-bar",
   435                        "servers": [('foo-bar-1', '2.2.2.3',
   436                                     3333, ["maxconn 4"])],
   437                        },
   438                   ]
   439                   }])
   440               },
   441          ]
   442  
   443          expected = {
   444              'service': {
   445                  'service_name': 'service',
   446                  'service_host': '0.0.0.0',
   447                  'service_port': 10002,
   448                  'servers': [
   449                      ['foo-0', '1.2.3.4', 4242, ["maxconn 4"]],
   450                      ['foo-0', '1.2.3.5', 4242, ["maxconn 4"]]
   451                      ],
   452                  'backends': [
   453                      {"backend_name": "foo-bar",
   454                       "servers": [
   455                           ['foo-bar-0', '2.2.2.2', 2222, ["maxconn 4"]],
   456                           ['foo-bar-1', '2.2.2.3', 3333, ["maxconn 4"]],
   457                       ],
   458                       },
   459                  ]
   460                  },
   461              }
   462          self.assertEqual(expected, hooks.create_services())
   463          self.write_service_config.assert_called_with(expected)
   464  
   465      @patch.dict(os.environ, {"JUJU_UNIT_NAME": "foo/1"})
   466      def test_with_multiple_units_in_relation_scaleout(self):
   467          """
   468          Test multiple units in scaleout mode.
   469          Ensure no indirection layer gets created.
   470          """
   471          self.config_get.return_value["peering_mode"] = "active-active"
   472          self.get_config_services.return_value = {
   473              None: {
   474                  "service_name": "service",
   475                  },
   476              }
   477          unit_get = self.patch_hook("unit_get")
   478          unit_get.return_value = "1.2.4.5"
   479          self.relations_of_type.return_value = [
   480              {"port": 4242,
   481               "private-address": "1.2.4.4",
   482               "__unit__": "foo/0",
   483               "services": yaml.safe_dump([{
   484                   "service_name": "service",
   485                   "servers": [('foo-0', '1.2.3.4',
   486                                4242, ["maxconn 4"])]
   487                   }])
   488               },
   489  
   490              {"__unit__": "foo/1",
   491               "hostname": "foo-1",
   492               "private-address": "1.2.4.4",
   493               "all_services": yaml.dump([
   494                   {"service_name": "service",
   495                    "service_host": "0.0.0.0",
   496                    "service_options": ["balance leastconn"],
   497                    "service_port": 4242},
   498                   ])
   499               },
   500          ]
   501  
   502          expected = {
   503              'service': {
   504                  'service_name': 'service',
   505                  'service_host': '0.0.0.0',
   506                  'service_port': 10002,
   507                  'servers': [
   508                      ['foo-0', '1.2.3.4', 4242, ["maxconn 4"]],
   509                      ]
   510                  },
   511              }
   512          self.assertEqual(expected, hooks.create_services())
   513          self.write_service_config.assert_called_with(expected)
   514  
   515      def test_merge_service(self):
   516          """ Make sure merge_services maintains "server" entries. """
   517          s1 = {'service_name': 'f', 'servers': [['f', '4', 4, ['maxconn 4']]]}
   518          s2 = {'service_name': 'f', 'servers': [['f', '5', 5, ['maxconn 4']]]}
   519  
   520          expected = {'service_name': 'f', 'servers': [
   521              ['f', '4', 4, ['maxconn 4']],
   522              ['f', '5', 5, ['maxconn 4']]]}
   523  
   524          self.assertEqual(expected, hooks.merge_service(s1, s2))
   525  
   526      def test_merge_service_removes_duplicates(self):
   527          """
   528          Make sure merge services strips strict duplicates from the
   529          'servers' entries.
   530          """
   531          s1 = {'servers': [['f', '4', 4, ['maxconn 4']]]}
   532          s2 = {'servers': [['f', '4', 4, ['maxconn 4']]]}
   533          expected = {'servers': [['f', '4', 4, ['maxconn 4']]]}
   534          self.assertEqual(expected, hooks.merge_service(s1, s2))
   535  
   536      def test_merge_service_merge_order(self):
   537          """ Make sure merge_services prefers the left side. """
   538          s1 = {'service_name': 'left', 'foo': 'bar'}
   539          s2 = {'service_name': 'right', 'bar': 'baz'}
   540  
   541          expected = {'service_name': 'left', 'foo': 'bar', 'bar': 'baz'}
   542          self.assertEqual(expected, hooks.merge_service(s1, s2))
   543  
   544      def test_merge_service_old_backend_without_name(self):
   545          """Backends in old_service without name raise an exception."""
   546  
   547          s1 = {'backends': [{'servers': []}]}
   548          s2 = {'backends': []}
   549          self.assertRaises(
   550              hooks.InvalidRelationDataError, hooks.merge_service, s1, s2)
   551  
   552      def test_merge_service_new_backend_without_name(self):
   553          """Backends in new_service without name raise an exception."""
   554  
   555          s1 = {'backends': []}
   556          s2 = {'backends': [{'servers': []}]}
   557          self.assertRaises(
   558              hooks.InvalidRelationDataError, hooks.merge_service, s1, s2)
   559  
   560      def test_merge_service_no_old_backend(self):
   561          """
   562          If the old service config has no backends, the backends from the
   563          new config is used..
   564          """
   565  
   566          s1 = {}
   567          s2 = {'backends': [
   568              {'backend_name': 'webapp',
   569               'servers': [['webapp-1', '10.0.0.2', 8090, []]]},
   570          ]}
   571          self.assertEqual(s2, hooks.merge_service(s1, s2))
   572  
   573      def test_merge_service_no_new_backend(self):
   574          """
   575          If the new service config has no backends, the backends from the
   576          old config is used..
   577          """
   578  
   579          s1 = {'backends': [
   580              {'backend_name': 'webapp',
   581               'servers': [['webapp-1', '10.0.0.2', 8090, []]]},
   582          ]}
   583          s2 = {}
   584          self.assertEqual(s1, hooks.merge_service(s1, s2))
   585  
   586      def test_merge_service_backend_name_matching(self):
   587          """Backends are merged by backend_name."""
   588  
   589          s1 = {'backends': [
   590              {'backend_name': 'api',
   591               'servers': [['api-0', '10.0.0.1', 9080, []]]},
   592              {'backend_name': 'webapp',
   593               'servers': [['webapp-0', '10.0.0.1', 8090, []]]},
   594          ]}
   595          s2 = {'backends': [
   596              {'backend_name': 'webapp',
   597               'servers': [['webapp-1', '10.0.0.2', 8090, []]]},
   598          ]}
   599          expected = {
   600              'backends': [
   601                  {'backend_name': 'api',
   602                   'servers': [['api-0', '10.0.0.1', 9080, []]]},
   603                  {'backend_name': 'webapp',
   604                   'servers': [['webapp-0', '10.0.0.1', 8090, []],
   605                               ['webapp-1', '10.0.0.2', 8090, []]]},
   606              ]
   607          }
   608          self.assertEqual(expected, hooks.merge_service(s1, s2))
   609  
   610      def test_join_reverseproxy_relation(self):
   611          """
   612          When haproxy joins a reverseproxy relation it advertises its public
   613          IP and public certificate by setting values on the relation.
   614          """
   615          ssl_cert = base64.b64encode("<cert data>")
   616          self.config_get.return_value = {"ssl_cert": ssl_cert}
   617          unit_get = self.patch_hook("unit_get")
   618          unit_get.return_value = "1.2.3.4"
   619          relation_id = self.patch_hook("relation_id")
   620          relation_id.return_value = "reverseproxy:1"
   621          relation_set = self.patch_hook("relation_set")
   622          hooks.reverseproxy_interface(hook_name="joined")
   623          unit_get.assert_called_once_with("public-address")
   624          relation_set.assert_called_once_with(
   625              relation_id="reverseproxy:1",
   626              relation_settings={
   627                  "public-address": "1.2.3.4",
   628                  "ssl_cert": ssl_cert})
   629  
   630      def test_join_reverseproxy_relation_with_selfsigned_cert(self):
   631          """
   632          When haproxy joins a reverseproxy relation and a self-signed
   633          certificate is configured, then it's included in the relation.
   634          """
   635          self.config_get.return_value = {"ssl_cert": "SELFSIGNED"}
   636          unit_get = self.patch_hook("unit_get")
   637          unit_get.return_value = "1.2.3.4"
   638          relation_id = self.patch_hook("relation_id")
   639          relation_id.return_value = "reverseproxy:1"
   640          get_selfsigned_cert = self.patch_hook("get_selfsigned_cert")
   641          get_selfsigned_cert.return_value = ("<self-signed>", None)
   642          relation_set = self.patch_hook("relation_set")
   643          hooks.reverseproxy_interface(hook_name="joined")
   644          unit_get.assert_called_once_with("public-address")
   645          ssl_cert = base64.b64encode("<self-signed>")
   646          relation_set.assert_called_once_with(
   647              relation_id="reverseproxy:1",
   648              relation_settings={
   649                  "public-address": "1.2.3.4",
   650                  "ssl_cert": ssl_cert})