github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/jujupy/tests/test_status.py (about)

     1  from collections import defaultdict
     2  from datetime import (
     3      datetime,
     4      timedelta,
     5      )
     6  from dateutil import tz
     7  try:
     8      from mock import patch
     9  except ImportError:
    10      from unittest.mock import patch
    11  import types
    12  
    13  from jujupy.exceptions import (
    14      AgentError,
    15      AgentUnresolvedError,
    16      AppError,
    17      ErroredUnit,
    18      HookFailedError,
    19      InstallError,
    20      MachineError,
    21      ProvisioningError,
    22      StatusError,
    23      StuckAllocatingError,
    24      UnitError,
    25  )
    26  from jujupy.tests.test_client import (
    27      TestModelClient,
    28  )
    29  from jujupy.status import (
    30      Status,
    31      StatusItem
    32      )
    33  from tests import (
    34      TestCase,
    35      FakeHomeTestCase,
    36  )
    37  
    38  
    39  class TestStatusItem(TestCase):
    40  
    41      @staticmethod
    42      def make_status_item(status_name, item_name, **kwargs):
    43          return StatusItem(status_name, item_name, {status_name: kwargs})
    44  
    45      def assertIsType(self, obj, target_type):
    46          self.assertIs(type(obj), target_type)
    47  
    48      def test_datetime_since(self):
    49          item = self.make_status_item(StatusItem.JUJU, '0',
    50                                       since='19 Aug 2016 05:36:42Z')
    51          target = datetime(2016, 8, 19, 5, 36, 42, tzinfo=tz.gettz('UTC'))
    52          self.assertEqual(item.datetime_since, target)
    53  
    54      def test_datetime_since_lxd(self):
    55          UTC = tz.gettz('UTC')
    56          item = self.make_status_item(StatusItem.JUJU, '0',
    57                                       since='30 Nov 2016 09:58:43-05:00')
    58          target = datetime(2016, 11, 30, 14, 58, 43, tzinfo=UTC)
    59          self.assertEqual(item.datetime_since.astimezone(UTC), target)
    60  
    61      def test_datetime_since_none(self):
    62          item = self.make_status_item(StatusItem.JUJU, '0')
    63          self.assertIsNone(item.datetime_since)
    64  
    65      def test_to_exception_good(self):
    66          item = self.make_status_item(StatusItem.JUJU, '0', current='idle')
    67          self.assertIsNone(item.to_exception())
    68  
    69      def test_to_exception_machine_error(self):
    70          item = self.make_status_item(StatusItem.MACHINE, '0', current='error')
    71          self.assertIsType(item.to_exception(), MachineError)
    72  
    73      def test_to_exception_provisioning_error(self):
    74          item = self.make_status_item(StatusItem.MACHINE, '0',
    75                                       current='provisioning error')
    76          self.assertIsType(item.to_exception(), ProvisioningError)
    77  
    78      def test_to_exception_stuck_allocating(self):
    79          item = self.make_status_item(StatusItem.MACHINE, '0',
    80                                       current='allocating', message='foo')
    81          with self.assertRaisesRegexp(
    82                  StuckAllocatingError,
    83                  "\('0', 'Stuck allocating.  Last message: foo'\)"):
    84              raise item.to_exception()
    85  
    86      def test_to_exception_allocating_unit(self):
    87          item = self.make_status_item(StatusItem.JUJU, '0',
    88                                       current='allocating', message='foo')
    89          self.assertIs(None, item.to_exception())
    90  
    91      def test_to_exception_app_error(self):
    92          item = self.make_status_item(StatusItem.APPLICATION, '0',
    93                                       current='error')
    94          self.assertIsType(item.to_exception(), AppError)
    95  
    96      def test_to_exception_unit_error(self):
    97          item = self.make_status_item(StatusItem.WORKLOAD, 'fake/0',
    98                                       current='error',
    99                                       message='generic unit error')
   100          self.assertIsType(item.to_exception(), UnitError)
   101  
   102      def test_to_exception_hook_failed_error(self):
   103          item = self.make_status_item(StatusItem.WORKLOAD, 'fake/0',
   104                                       current='error',
   105                                       message='hook failed: "bad hook"')
   106          self.assertIsType(item.to_exception(), HookFailedError)
   107  
   108      def test_to_exception_install_error(self):
   109          item = self.make_status_item(StatusItem.WORKLOAD, 'fake/0',
   110                                       current='error',
   111                                       message='hook failed: "install error"')
   112          self.assertIsType(item.to_exception(), InstallError)
   113  
   114      def make_agent_item_ago(self, minutes):
   115          now = datetime.utcnow()
   116          then = now - timedelta(minutes=minutes)
   117          then_str = then.strftime('%d %b %Y %H:%M:%SZ')
   118          return self.make_status_item(StatusItem.JUJU, '0', current='error',
   119                                       message='some error', since=then_str)
   120  
   121      def test_to_exception_agent_error(self):
   122          item = self.make_agent_item_ago(minutes=3)
   123          self.assertIsType(item.to_exception(), AgentError)
   124  
   125      def test_to_exception_agent_error_no_since(self):
   126          item = self.make_status_item(StatusItem.JUJU, '0', current='error')
   127          self.assertIsType(item.to_exception(), AgentError)
   128  
   129      def test_to_exception_agent_unresolved_error(self):
   130          item = self.make_agent_item_ago(minutes=6)
   131          self.assertIsType(item.to_exception(), AgentUnresolvedError)
   132  
   133  
   134  class TestStatus(FakeHomeTestCase):
   135  
   136      def test_model_name(self):
   137          status = Status({'model': {'name': 'bar'}}, '')
   138          self.assertEqual('bar', status.model_name)
   139  
   140      def test_iter_machines_no_containers(self):
   141          status = Status({
   142              'machines': {
   143                  '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
   144              },
   145              'applications': {}}, '')
   146          self.assertEqual(list(status.iter_machines()),
   147                           [('1', status.status['machines']['1'])])
   148  
   149      def test_iter_machines_containers(self):
   150          status = Status({
   151              'machines': {
   152                  '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
   153              },
   154              'applications': {}}, '')
   155          self.assertEqual(list(status.iter_machines(containers=True)), [
   156              ('1', status.status['machines']['1']),
   157              ('1/lxc/0', {'baz': 'qux'}),
   158          ])
   159  
   160      def test__iter_units_in_application(self):
   161          status = Status({}, '')
   162          app_status = {
   163              'units': {'jenkins/1': {'subordinates': {'sub': {'baz': 'qux'}}}}
   164              }
   165          expected = [
   166              ('jenkins/1', {'subordinates': {'sub': {'baz': 'qux'}}}),
   167              ('sub', {'baz': 'qux'})]
   168          self.assertItemsEqual(expected,
   169                                status._iter_units_in_application(app_status))
   170  
   171      def test_agent_items_empty(self):
   172          status = Status({'machines': {}, 'applications': {}}, '')
   173          self.assertItemsEqual([], status.agent_items())
   174  
   175      def test_agent_items(self):
   176          status = Status({
   177              'machines': {
   178                  '1': {'foo': 'bar'}
   179              },
   180              'applications': {
   181                  'jenkins': {
   182                      'units': {
   183                          'jenkins/1': {
   184                              'subordinates': {
   185                                  'sub': {'baz': 'qux'}
   186                              }
   187                          }
   188                      }
   189                  }
   190              }
   191          }, '')
   192          expected = [
   193              ('1', {'foo': 'bar'}),
   194              ('jenkins/1', {'subordinates': {'sub': {'baz': 'qux'}}}),
   195              ('sub', {'baz': 'qux'})]
   196          self.assertItemsEqual(expected, status.agent_items())
   197  
   198      def test_agent_items_containers(self):
   199          status = Status({
   200              'machines': {
   201                  '1': {'foo': 'bar', 'containers': {
   202                      '2': {'qux': 'baz'},
   203                      }}
   204                  },
   205              'applications': {}
   206              }, '')
   207          expected = [
   208              ('1', {'foo': 'bar', 'containers': {'2': {'qux': 'baz'}}}),
   209              ('2', {'qux': 'baz'})
   210              ]
   211          self.assertItemsEqual(expected, status.agent_items())
   212  
   213      def get_unit_agent_states_data(self):
   214          status = Status({
   215              'applications': {
   216                  'jenkins': {
   217                      'units': {'jenkins/0': {'agent-state': 'good'},
   218                                'jenkins/1': {'agent-state': 'bad'}},
   219                      },
   220                  'fakejob': {
   221                      'life': 'dying',
   222                      'units': {'fakejob/0': {'agent-state': 'good'}},
   223                      },
   224                  }
   225              }, '')
   226          expected = {
   227              'good': ['jenkins/0'],
   228              'bad': ['jenkins/1'],
   229              'dying': ['fakejob/0'],
   230              }
   231          return status, expected
   232  
   233      def test_unit_agent_states_new(self):
   234          (status, expected) = self.get_unit_agent_states_data()
   235          actual = status.unit_agent_states()
   236          self.assertEqual(expected, actual)
   237  
   238      def test_unit_agent_states_existing(self):
   239          (status, expected) = self.get_unit_agent_states_data()
   240          actual = defaultdict(list)
   241          status.unit_agent_states(actual)
   242          self.assertEqual(expected, actual)
   243  
   244      def test_get_service_count_zero(self):
   245          status = Status({
   246              'machines': {
   247                  '1': {'agent-state': 'good'},
   248                  '2': {},
   249                  },
   250              }, '')
   251          self.assertEqual(0, status.get_service_count())
   252  
   253      def test_get_service_count(self):
   254          status = Status({
   255              'machines': {
   256                  '1': {'agent-state': 'good'},
   257                  '2': {},
   258              },
   259              'applications': {
   260                  'jenkins': {
   261                      'units': {
   262                          'jenkins/1': {'agent-state': 'bad'},
   263                      }
   264                  },
   265                  'dummy-sink': {
   266                      'units': {
   267                          'dummy-sink/0': {'agent-state': 'started'},
   268                      }
   269                  },
   270                  'juju-reports': {
   271                      'units': {
   272                          'juju-reports/0': {'agent-state': 'pending'},
   273                      }
   274                  }
   275              }
   276          }, '')
   277          self.assertEqual(3, status.get_service_count())
   278  
   279      def test_get_service_unit_count_zero(self):
   280          status = Status({
   281              'machines': {
   282                  '1': {'agent-state': 'good'},
   283                  '2': {},
   284              },
   285          }, '')
   286          self.assertEqual(0, status.get_service_unit_count('jenkins'))
   287  
   288      def test_get_service_unit_count(self):
   289          status = Status({
   290              'machines': {
   291                  '1': {'agent-state': 'good'},
   292                  '2': {},
   293              },
   294              'applications': {
   295                  'jenkins': {
   296                      'units': {
   297                          'jenkins/1': {'agent-state': 'bad'},
   298                          'jenkins/2': {'agent-state': 'bad'},
   299                          'jenkins/3': {'agent-state': 'bad'},
   300                      }
   301                  }
   302              }
   303          }, '')
   304          self.assertEqual(3, status.get_service_unit_count('jenkins'))
   305  
   306      def test_get_unit(self):
   307          status = Status({
   308              'machines': {
   309                  '1': {},
   310              },
   311              'applications': {
   312                  'jenkins': {
   313                      'units': {
   314                          'jenkins/1': {'agent-state': 'bad'},
   315                      }
   316                  },
   317                  'dummy-sink': {
   318                      'units': {
   319                          'jenkins/2': {'agent-state': 'started'},
   320                      }
   321                  },
   322              }
   323          }, '')
   324          self.assertEqual(
   325              status.get_unit('jenkins/1'), {'agent-state': 'bad'})
   326          self.assertEqual(
   327              status.get_unit('jenkins/2'), {'agent-state': 'started'})
   328          with self.assertRaisesRegexp(KeyError, 'jenkins/3'):
   329              status.get_unit('jenkins/3')
   330  
   331      def test_service_subordinate_units(self):
   332          status = Status({
   333              'machines': {
   334                  '1': {},
   335              },
   336              'applications': {
   337                  'ubuntu': {},
   338                  'jenkins': {
   339                      'units': {
   340                          'jenkins/1': {
   341                              'subordinates': {
   342                                  'chaos-monkey/0': {'agent-state': 'started'},
   343                              }
   344                          }
   345                      }
   346                  },
   347                  'dummy-sink': {
   348                      'units': {
   349                          'jenkins/2': {
   350                              'subordinates': {
   351                                  'chaos-monkey/1': {'agent-state': 'started'}
   352                              }
   353                          },
   354                          'jenkins/3': {
   355                              'subordinates': {
   356                                  'chaos-monkey/2': {'agent-state': 'started'}
   357                              }
   358                          }
   359                      }
   360                  }
   361              }
   362          }, '')
   363          self.assertItemsEqual(
   364              status.service_subordinate_units('ubuntu'),
   365              [])
   366          self.assertItemsEqual(
   367              status.service_subordinate_units('jenkins'),
   368              [('chaos-monkey/0', {'agent-state': 'started'},)])
   369          self.assertItemsEqual(
   370              status.service_subordinate_units('dummy-sink'), [
   371                  ('chaos-monkey/1', {'agent-state': 'started'}),
   372                  ('chaos-monkey/2', {'agent-state': 'started'})]
   373              )
   374  
   375      def test_get_open_ports(self):
   376          status = Status({
   377              'machines': {
   378                  '1': {},
   379              },
   380              'applications': {
   381                  'jenkins': {
   382                      'units': {
   383                          'jenkins/1': {'agent-state': 'bad'},
   384                      }
   385                  },
   386                  'dummy-sink': {
   387                      'units': {
   388                          'jenkins/2': {'open-ports': ['42/tcp']},
   389                      }
   390                  },
   391              }
   392          }, '')
   393          self.assertEqual(status.get_open_ports('jenkins/1'), [])
   394          self.assertEqual(status.get_open_ports('jenkins/2'), ['42/tcp'])
   395  
   396      def test_agent_states_with_agent_state(self):
   397          status = Status({
   398              'machines': {
   399                  '1': {'agent-state': 'good'},
   400                  '2': {},
   401              },
   402              'applications': {
   403                  'jenkins': {
   404                      'units': {
   405                          'jenkins/1': {'agent-state': 'bad'},
   406                          'jenkins/2': {'agent-state': 'good'},
   407                      }
   408                  }
   409              }
   410          }, '')
   411          expected = {
   412              'good': ['1', 'jenkins/2'],
   413              'bad': ['jenkins/1'],
   414              'no-agent': ['2'],
   415          }
   416          self.assertEqual(expected, status.agent_states())
   417  
   418      def test_agent_states_with_agent_status(self):
   419          status = Status({
   420              'machines': {
   421                  '1': {'agent-state': 'good'},
   422                  '2': {},
   423              },
   424              'applications': {
   425                  'jenkins': {
   426                      'units': {
   427                          'jenkins/1': {'agent-status': {'current': 'bad'}},
   428                          'jenkins/2': {'agent-status': {'current': 'good'}},
   429                          'jenkins/3': {},
   430                      }
   431                  }
   432              }
   433          }, '')
   434          expected = {
   435              'good': ['1', 'jenkins/2'],
   436              'bad': ['jenkins/1'],
   437              'no-agent': ['2', 'jenkins/3'],
   438          }
   439          self.assertEqual(expected, status.agent_states())
   440  
   441      def test_agent_states_with_juju_status(self):
   442          status = Status({
   443              'machines': {
   444                  '1': {'juju-status': {'current': 'good'}},
   445                  '2': {},
   446              },
   447              'applications': {
   448                  'jenkins': {
   449                      'units': {
   450                          'jenkins/1': {'juju-status': {'current': 'bad'}},
   451                          'jenkins/2': {'juju-status': {'current': 'good'}},
   452                          'jenkins/3': {},
   453                      }
   454                  }
   455              }
   456          }, '')
   457          expected = {
   458              'good': ['1', 'jenkins/2'],
   459              'bad': ['jenkins/1'],
   460              'no-agent': ['2', 'jenkins/3'],
   461          }
   462          self.assertEqual(expected, status.agent_states())
   463  
   464      def test_agent_states_with_dying(self):
   465          status = Status({
   466              'machines': {},
   467              'applications': {
   468                  'jenkins': {
   469                      'life': 'alive',
   470                      'units': {
   471                          'jenkins/1': {'juju-status': {'current': 'bad'}},
   472                          'jenkins/2': {'juju-status': {'current': 'good'}},
   473                          }
   474                      },
   475                  'fakejob': {
   476                      'life': 'dying',
   477                      'units': {
   478                          'fakejob/1': {'juju-status': {'current': 'bad'}},
   479                          'fakejob/2': {'juju-status': {'current': 'good'}},
   480                          }
   481                      },
   482                  }
   483              }, '')
   484          expected = {
   485              'good': ['jenkins/2'],
   486              'bad': ['jenkins/1'],
   487              'dying': ['fakejob/1', 'fakejob/2'],
   488              }
   489          self.assertEqual(expected, status.agent_states())
   490  
   491      def test_check_agents_started_not_started(self):
   492          status = Status({
   493              'machines': {
   494                  '1': {'agent-state': 'good'},
   495                  '2': {},
   496              },
   497              'applications': {
   498                  'jenkins': {
   499                      'units': {
   500                          'jenkins/1': {'agent-state': 'bad'},
   501                          'jenkins/2': {'agent-state': 'good'},
   502                      }
   503                  }
   504              }
   505          }, '')
   506          self.assertEqual(status.agent_states(),
   507                           status.check_agents_started('env1'))
   508  
   509      def test_check_agents_started_all_started_with_agent_state(self):
   510          status = Status({
   511              'machines': {
   512                  '1': {'agent-state': 'started'},
   513                  '2': {'agent-state': 'started'},
   514              },
   515              'applications': {
   516                  'jenkins': {
   517                      'units': {
   518                          'jenkins/1': {
   519                              'agent-state': 'started',
   520                              'subordinates': {
   521                                  'sub1': {
   522                                      'agent-state': 'started'
   523                                  }
   524                              }
   525                          },
   526                          'jenkins/2': {'agent-state': 'started'},
   527                      }
   528                  }
   529              }
   530          }, '')
   531          self.assertIsNone(status.check_agents_started('env1'))
   532  
   533      def test_check_agents_started_all_started_with_agent_status(self):
   534          status = Status({
   535              'machines': {
   536                  '1': {'agent-state': 'started'},
   537                  '2': {'agent-state': 'started'},
   538              },
   539              'applications': {
   540                  'jenkins': {
   541                      'units': {
   542                          'jenkins/1': {
   543                              'agent-status': {'current': 'idle'},
   544                              'subordinates': {
   545                                  'sub1': {
   546                                      'agent-status': {'current': 'idle'}
   547                                  }
   548                              }
   549                          },
   550                          'jenkins/2': {'agent-status': {'current': 'idle'}},
   551                      }
   552                  }
   553              }
   554          }, '')
   555          self.assertIsNone(status.check_agents_started('env1'))
   556  
   557      def test_check_agents_started_dying(self):
   558          status = Status({
   559              'machines': {
   560                  '1': {'agent-state': 'started'},
   561                  '2': {'agent-state': 'started'},
   562                  },
   563              'applications': {
   564                  'jenkins': {
   565                      'units': {
   566                          'jenkins/1': {
   567                              'agent-status': {'current': 'idle'},
   568                              'subordinates': {
   569                                  'sub1': {
   570                                      'agent-status': {'current': 'idle'}
   571                                      }
   572                                  }
   573                              },
   574                          'jenkins/2': {'agent-status': {'current': 'idle'}},
   575                          },
   576                      'life': 'dying',
   577                      }
   578                  }
   579              }, '')
   580          self.assertEqual(status.agent_states(),
   581                           status.check_agents_started('env1'))
   582  
   583      def test_check_agents_started_agent_error(self):
   584          status = Status({
   585              'machines': {
   586                  '1': {'agent-state': 'any-error'},
   587              },
   588              'applications': {}
   589          }, '')
   590          with self.assertRaisesRegexp(ErroredUnit,
   591                                       '1 is in state any-error'):
   592              status.check_agents_started('env1')
   593  
   594      def do_check_agents_started_agent_state_info_failure(self, failure):
   595          status = Status({
   596              'machines': {'0': {
   597                  'agent-state-info': failure}},
   598              'applications': {},
   599          }, '')
   600          with self.assertRaises(ErroredUnit) as e_cxt:
   601              status.check_agents_started()
   602          e = e_cxt.exception
   603          self.assertEqual(
   604              str(e), '0 is in state {}'.format(failure))
   605          self.assertEqual(e.unit_name, '0')
   606          self.assertEqual(e.state, failure)
   607  
   608      def do_check_agents_started_juju_status_failure(self, failure):
   609          status = Status({
   610              'machines': {
   611                  '0': {
   612                      'juju-status': {
   613                          'current': 'error',
   614                          'message': failure}
   615                      },
   616                  }
   617              }, '')
   618          with self.assertRaises(ErroredUnit) as e_cxt:
   619              status.check_agents_started()
   620          e = e_cxt.exception
   621          # if message is blank, the failure should reflect the state instead
   622          if not failure:
   623              failure = 'error'
   624          self.assertEqual(
   625              str(e), '0 is in state {}'.format(failure))
   626          self.assertEqual(e.unit_name, '0')
   627          self.assertEqual(e.state, failure)
   628  
   629      def test_check_agents_started_read_juju_status_error(self):
   630          failures = ['no "centos7" images in us-east-1 with arches [amd64]',
   631                      'sending new instance request: GCE operation ' +
   632                      '"operation-143" failed', '']
   633          for failure in failures:
   634              self.do_check_agents_started_juju_status_failure(failure)
   635  
   636      def test_check_agents_started_read_agent_state_info_error(self):
   637          failures = ['cannot set up groups foobar', 'cannot run instance',
   638                      'cannot run instances', 'error executing "lxc-start"']
   639          for failure in failures:
   640              self.do_check_agents_started_agent_state_info_failure(failure)
   641  
   642      def test_check_agents_started_agent_info_error(self):
   643          # Sometimes the error is indicated in a special 'agent-state-info'
   644          # field.
   645          status = Status({
   646              'machines': {
   647                  '1': {'agent-state-info': 'any-error'},
   648              },
   649              'applications': {}
   650          }, '')
   651          with self.assertRaisesRegexp(ErroredUnit,
   652                                       '1 is in state any-error'):
   653              status.check_agents_started('env1')
   654  
   655      def test_get_agent_versions_1x(self):
   656          status = Status({
   657              'machines': {
   658                  '1': {'agent-version': '1.6.2'},
   659                  '2': {'agent-version': '1.6.1'},
   660              },
   661              'applications': {
   662                  'jenkins': {
   663                      'units': {
   664                          'jenkins/0': {
   665                              'agent-version': '1.6.1'},
   666                          'jenkins/1': {},
   667                      },
   668                  }
   669              }
   670          }, '')
   671          self.assertEqual({
   672              '1.6.2': {'1'},
   673              '1.6.1': {'jenkins/0', '2'},
   674              'unknown': {'jenkins/1'},
   675          }, status.get_agent_versions())
   676  
   677      def test_get_agent_versions_2x(self):
   678          status = Status({
   679              'machines': {
   680                  '1': {'juju-status': {'version': '1.6.2'}},
   681                  '2': {'juju-status': {'version': '1.6.1'}},
   682              },
   683              'applications': {
   684                  'jenkins': {
   685                      'units': {
   686                          'jenkins/0': {
   687                              'juju-status': {'version': '1.6.1'}},
   688                          'jenkins/1': {},
   689                      },
   690                  }
   691              }
   692          }, '')
   693          self.assertEqual({
   694              '1.6.2': {'1'},
   695              '1.6.1': {'jenkins/0', '2'},
   696              'unknown': {'jenkins/1'},
   697          }, status.get_agent_versions())
   698  
   699      def test_iter_new_machines(self):
   700          old_status = Status({
   701              'machines': {
   702                  'bar': 'bar_info',
   703              }
   704          }, '')
   705          new_status = Status({
   706              'machines': {
   707                  'foo': 'foo_info',
   708                  'bar': 'bar_info',
   709              }
   710          }, '')
   711          self.assertItemsEqual(new_status.iter_new_machines(old_status),
   712                                [('foo', 'foo_info')])
   713  
   714      def test_iter_new_machines_no_containers(self):
   715          bar_info = {'containers': {'bar/lxd/1': {}}}
   716          old_status = Status({
   717              'machines': {
   718                  'bar': bar_info,
   719              }
   720          }, '')
   721          foo_info = {'containers': {'foo/lxd/1': {}}}
   722          new_status = Status({
   723              'machines': {
   724                  'foo': foo_info,
   725                  'bar': bar_info,
   726              }
   727          }, '')
   728          self.assertItemsEqual(new_status.iter_new_machines(old_status,
   729                                                             containers=False),
   730                                [('foo', foo_info)])
   731  
   732      def test_iter_new_machines_with_containers(self):
   733          bar_info = {'containers': {'bar/lxd/1': {}}}
   734          old_status = Status({
   735              'machines': {
   736                  'bar': bar_info,
   737              }
   738          }, '')
   739          foo_info = {'containers': {'foo/lxd/1': {}}}
   740          new_status = Status({
   741              'machines': {
   742                  'foo': foo_info,
   743                  'bar': bar_info,
   744              }
   745          }, '')
   746          self.assertItemsEqual(new_status.iter_new_machines(old_status,
   747                                                             containers=True),
   748                                [('foo', foo_info), ('foo/lxd/1', {})])
   749  
   750      def test_get_instance_id(self):
   751          status = Status({
   752              'machines': {
   753                  '0': {'instance-id': 'foo-bar'},
   754                  '1': {},
   755              }
   756          }, '')
   757          self.assertEqual(status.get_instance_id('0'), 'foo-bar')
   758          with self.assertRaises(KeyError):
   759              status.get_instance_id('1')
   760          with self.assertRaises(KeyError):
   761              status.get_instance_id('2')
   762  
   763      def test_get_machine_dns_name(self):
   764          status = Status({
   765              'machines': {
   766                  '0': {'dns-name': '255.1.1.0'},
   767                  '1': {},
   768              }
   769          }, '')
   770          self.assertEqual(status.get_machine_dns_name('0'), '255.1.1.0')
   771          with self.assertRaisesRegexp(KeyError, 'dns-name'):
   772              status.get_machine_dns_name('1')
   773          with self.assertRaisesRegexp(KeyError, '2'):
   774              status.get_machine_dns_name('2')
   775  
   776      def test_from_text(self):
   777          text = TestModelClient.make_status_yaml(
   778              'agent-state', 'pending', 'horsefeathers').decode('ascii')
   779          status = Status.from_text(text)
   780          self.assertEqual(status.status_text, text)
   781          self.assertEqual(status.status, {
   782              'model': {'name': 'foo'},
   783              'machines': {'0': {'agent-state': 'pending'}},
   784              'applications': {'jenkins': {'units': {'jenkins/0': {
   785                  'agent-state': 'horsefeathers'}}}}
   786          })
   787  
   788      def test_iter_units(self):
   789          started_unit = {'agent-state': 'started'}
   790          unit_with_subordinates = {
   791              'agent-state': 'started',
   792              'subordinates': {
   793                  'ntp/0': started_unit,
   794                  'nrpe/0': started_unit,
   795              },
   796          }
   797          status = Status({
   798              'machines': {
   799                  '1': {'agent-state': 'started'},
   800              },
   801              'applications': {
   802                  'jenkins': {
   803                      'units': {
   804                          'jenkins/0': unit_with_subordinates,
   805                      }
   806                  },
   807                  'application': {
   808                      'units': {
   809                          'application/0': started_unit,
   810                          'application/1': started_unit,
   811                      }
   812                  },
   813              }
   814          }, '')
   815          expected = [
   816              ('application/0', started_unit),
   817              ('application/1', started_unit),
   818              ('jenkins/0', unit_with_subordinates),
   819              ('nrpe/0', started_unit),
   820              ('ntp/0', started_unit),
   821          ]
   822          gen = status.iter_units()
   823          self.assertIsInstance(gen, types.GeneratorType)
   824          self.assertEqual(expected, list(gen))
   825  
   826      @staticmethod
   827      def run_iter_status():
   828          status = Status({
   829              'machines': {
   830                  '0': {
   831                      'juju-status': {
   832                          'current': 'idle',
   833                          'since': 'DD MM YYYY hh:mm:ss',
   834                          'version': '2.0.0',
   835                          },
   836                      'machine-status': {
   837                          'current': 'running',
   838                          'message': 'Running',
   839                          'since': 'DD MM YYYY hh:mm:ss',
   840                          },
   841                      },
   842                  '1': {
   843                      'juju-status': {
   844                          'current': 'idle',
   845                          'since': 'DD MM YYYY hh:mm:ss',
   846                          'version': '2.0.0',
   847                          },
   848                      'machine-status': {
   849                          'current': 'running',
   850                          'message': 'Running',
   851                          'since': 'DD MM YYYY hh:mm:ss',
   852                          },
   853                      },
   854                  },
   855              'applications': {
   856                  'fakejob': {
   857                      'application-status': {
   858                          'current': 'idle',
   859                          'since': 'DD MM YYYY hh:mm:ss',
   860                          },
   861                      'units': {
   862                          'fakejob/0': {
   863                              'workload-status': {
   864                                  'current': 'maintenance',
   865                                  'message': 'Started',
   866                                  'since': 'DD MM YYYY hh:mm:ss',
   867                                  },
   868                              'juju-status': {
   869                                  'current': 'idle',
   870                                  'since': 'DD MM YYYY hh:mm:ss',
   871                                  'version': '2.0.0',
   872                                  },
   873                              },
   874                          'fakejob/1': {
   875                              'workload-status': {
   876                                  'current': 'maintenance',
   877                                  'message': 'Started',
   878                                  'since': 'DD MM YYYY hh:mm:ss',
   879                                  },
   880                              'juju-status': {
   881                                  'current': 'idle',
   882                                  'since': 'DD MM YYYY hh:mm:ss',
   883                                  'version': '2.0.0',
   884                                  },
   885                              },
   886                          },
   887                      }
   888                  },
   889              }, '')
   890          for sub_status in status.iter_status():
   891              yield sub_status
   892  
   893      def test_iter_status_range(self):
   894          status_set = set([(status_item.item_name, status_item.status_name)
   895                            for status_item in self.run_iter_status()])
   896          self.assertEqual({
   897              ('0', 'juju-status'), ('0', 'machine-status'),
   898              ('1', 'juju-status'), ('1', 'machine-status'),
   899              ('fakejob', 'application-status'),
   900              ('fakejob/0', 'workload-status'), ('fakejob/0', 'juju-status'),
   901              ('fakejob/1', 'workload-status'), ('fakejob/1', 'juju-status'),
   902              }, status_set)
   903  
   904      def test_iter_status_data(self):
   905          min_set = set(['current', 'since'])
   906          max_set = set(['current', 'message', 'since', 'version'])
   907          for status_item in self.run_iter_status():
   908              if 'fakejob' == status_item.item_name:
   909                  self.assertEqual(StatusItem.APPLICATION,
   910                                   status_item.status_name)
   911                  self.assertEqual({'current': 'idle',
   912                                    'since': 'DD MM YYYY hh:mm:ss',
   913                                    }, status_item.status)
   914              else:
   915                  cur_set = set(status_item.status.keys())
   916                  self.assertTrue(min_set < cur_set)
   917                  self.assertTrue(cur_set < max_set)
   918  
   919      def test_iter_status_container(self):
   920          status_dict = {'machines': {'0': {
   921              'containers': {'0/lxd/0': {
   922                  'machine-status': 'foo',
   923                  'juju-status': 'bar',
   924                  }}
   925              }}}
   926          status = Status(status_dict, '')
   927          machine_0_data = status.status['machines']['0']
   928          container_data = machine_0_data['containers']['0/lxd/0']
   929          self.assertEqual([
   930              StatusItem(StatusItem.MACHINE, '0', machine_0_data),
   931              StatusItem(StatusItem.JUJU, '0', machine_0_data),
   932              StatusItem(StatusItem.MACHINE, '0/lxd/0', container_data),
   933              StatusItem(StatusItem.JUJU, '0/lxd/0', container_data),
   934              ], list(status.iter_status()))
   935  
   936      def test_iter_status_subordinate(self):
   937          status_dict = {
   938              'machines': {},
   939              'applications': {
   940                  'dummy': {
   941                      'units': {'dummy/0': {
   942                          'subordinates': {
   943                              'dummy-sub/0': {}
   944                              }
   945                          }},
   946                      }
   947                  },
   948              }
   949          status = Status(status_dict, '')
   950          dummy_data = status.status['applications']['dummy']
   951          dummy_0_data = dummy_data['units']['dummy/0']
   952          dummy_sub_0_data = dummy_0_data['subordinates']['dummy-sub/0']
   953          self.assertEqual([
   954              StatusItem(StatusItem.APPLICATION, 'dummy', dummy_data),
   955              StatusItem(StatusItem.WORKLOAD, 'dummy/0', dummy_0_data),
   956              StatusItem(StatusItem.JUJU, 'dummy/0', dummy_0_data),
   957              StatusItem(StatusItem.WORKLOAD, 'dummy-sub/0', dummy_sub_0_data),
   958              StatusItem(StatusItem.JUJU, 'dummy-sub/0', dummy_sub_0_data),
   959              ], list(status.iter_status()))
   960  
   961      def test_iter_errors(self):
   962          status = Status({}, '')
   963          retval = [
   964              StatusItem(StatusItem.WORKLOAD, 'job/0', {'current': 'started'}),
   965              StatusItem(StatusItem.APPLICATION, 'job', {'current': 'started'}),
   966              StatusItem(StatusItem.MACHINE, '0', {'current': 'error'}),
   967              ]
   968          with patch.object(status, 'iter_status', autospec=True,
   969                            return_value=retval):
   970              errors = list(status.iter_errors())
   971          self.assertEqual(len(errors), 1)
   972          self.assertIsInstance(errors[0], MachineError)
   973          self.assertEqual(('0', None), errors[0].args)
   974  
   975      def test_iter_errors_ignore_recoverable(self):
   976          status = Status({}, '')
   977          retval = [
   978              StatusItem(StatusItem.WORKLOAD, 'job/0', {'current': 'error'}),
   979              StatusItem(StatusItem.MACHINE, '0', {'current': 'error'}),
   980              ]
   981          with patch.object(status, 'iter_status', autospec=True,
   982                            return_value=retval):
   983              errors = list(status.iter_errors(ignore_recoverable=True))
   984          self.assertEqual(len(errors), 1)
   985          self.assertIsInstance(errors[0], MachineError)
   986          self.assertEqual(('0', None), errors[0].args)
   987          with patch.object(status, 'iter_status', autospec=True,
   988                            return_value=retval):
   989              recoverable = list(status.iter_errors())
   990          self.assertGreater(len(recoverable), len(errors))
   991  
   992      def test_check_for_errors_good(self):
   993          status = Status({}, '')
   994          with patch.object(status, 'iter_errors', autospec=True,
   995                            return_value=[]) as error_mock:
   996              self.assertEqual([], status.check_for_errors())
   997          error_mock.assert_called_once_with(False)
   998  
   999      def test_check_for_errors(self):
  1000          status = Status({}, '')
  1001          errors = [MachineError('0'), StatusError('2'), UnitError('1')]
  1002          with patch.object(status, 'iter_errors', autospec=True,
  1003                            return_value=errors) as errors_mock:
  1004              sorted_errors = status.check_for_errors()
  1005          errors_mock.assert_called_once_with(False)
  1006          self.assertEqual(sorted_errors[0].args, ('0',))
  1007          self.assertEqual(sorted_errors[1].args, ('1',))
  1008          self.assertEqual(sorted_errors[2].args, ('2',))
  1009  
  1010      def test_raise_highest_error(self):
  1011          status = Status({}, '')
  1012          retval = [
  1013              StatusItem(StatusItem.WORKLOAD, 'job/0', {'current': 'error'}),
  1014              StatusItem(StatusItem.MACHINE, '0', {'current': 'error'}),
  1015              ]
  1016          with patch.object(status, 'iter_status', autospec=True,
  1017                            return_value=retval):
  1018              with self.assertRaises(MachineError):
  1019                  status.raise_highest_error()
  1020  
  1021      def test_raise_highest_error_ignore_recoverable(self):
  1022          status = Status({}, '')
  1023          retval = [
  1024              StatusItem(StatusItem.WORKLOAD, 'job/0', {'current': 'error'})]
  1025          with patch.object(status, 'iter_status', autospec=True,
  1026                            return_value=retval):
  1027              status.raise_highest_error(ignore_recoverable=True)
  1028              with self.assertRaises(UnitError):
  1029                  status.raise_highest_error(ignore_recoverable=False)
  1030  
  1031      def test_get_applications_gets_applications(self):
  1032          status = Status({
  1033              'services': {'service': {}},
  1034              'applications': {'application': {}},
  1035              }, '')
  1036          self.assertEqual({'application': {}}, status.get_applications())