Coverage for nova/servicegroup/drivers/db.py: 87%

49 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2012 IBM Corp. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 

12# implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15 

16from oslo_log import log as logging 

17import oslo_messaging as messaging 

18from oslo_utils import timeutils 

19 

20import nova.conf 

21from nova import exception 

22from nova.i18n import _ 

23from nova.servicegroup import api 

24from nova.servicegroup.drivers import base 

25 

26 

27CONF = nova.conf.CONF 

28 

29LOG = logging.getLogger(__name__) 

30 

31 

32class DbDriver(base.Driver): 

33 

34 def __init__(self, *args, **kwargs): 

35 self.service_down_time = CONF.service_down_time 

36 

37 def join(self, member, group, service=None): 

38 """Add a new member to a service group. 

39 

40 :param member: the joined member ID/name 

41 :param group: the group ID/name, of the joined member 

42 :param service: a `nova.service.Service` object 

43 """ 

44 LOG.debug('DB_Driver: join new ServiceGroup member %(member)s to ' 

45 'the %(group)s group, service = %(service)s', 

46 {'member': member, 'group': group, 

47 'service': service}) 

48 if service is None: 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true

49 raise RuntimeError(_('service is a mandatory argument for DB based' 

50 ' ServiceGroup driver')) 

51 report_interval = service.report_interval 

52 if report_interval: 

53 service.tg.add_timer_args( 

54 report_interval, self._report_state, args=[service], 

55 initial_delay=api.INITIAL_REPORTING_DELAY) 

56 

57 def is_up(self, service_ref): 

58 """Moved from nova.utils 

59 Check whether a service is up based on last heartbeat. 

60 """ 

61 last_heartbeat = (service_ref.get('last_seen_up') or 

62 service_ref['created_at']) 

63 if isinstance(last_heartbeat, str): 63 ↛ 67line 63 didn't jump to line 67 because the condition on line 63 was never true

64 # NOTE(russellb) If this service_ref came in over rpc via 

65 # conductor, then the timestamp will be a string and needs to be 

66 # converted back to a datetime. 

67 last_heartbeat = timeutils.parse_strtime(last_heartbeat) 

68 else: 

69 # Objects have proper UTC timezones, but the timeutils comparison 

70 # below does not (and will fail) 

71 last_heartbeat = last_heartbeat.replace(tzinfo=None) 

72 # Timestamps in DB are UTC. 

73 elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow()) 

74 is_up = abs(elapsed) <= self.service_down_time 

75 if not is_up: 

76 LOG.debug('Seems service %(binary)s on host %(host)s is down. ' 

77 'Last heartbeat was %(lhb)s. Elapsed time is %(el)s', 

78 {'binary': service_ref.get('binary'), 

79 'host': service_ref.get('host'), 

80 'lhb': str(last_heartbeat), 'el': str(elapsed)}) 

81 return is_up 

82 

83 def updated_time(self, service_ref): 

84 """Get the updated time from db""" 

85 return service_ref['updated_at'] 

86 

87 def _report_state(self, service): 

88 """Update the state of this service in the datastore.""" 

89 

90 try: 

91 service.service_ref.report_count += 1 

92 service.service_ref.save() 

93 

94 # TODO(termie): make this pattern be more elegant. 

95 if getattr(service, 'model_disconnected', False): 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

96 service.model_disconnected = False 

97 LOG.info('Recovered from being unable to report status.') 

98 except messaging.MessagingTimeout: 

99 # NOTE(johngarbutt) during upgrade we will see messaging timeouts 

100 # as nova-conductor is restarted, so only log this error once. 

101 if not getattr(service, 'model_disconnected', False): 101 ↛ exitline 101 didn't return from function '_report_state' because the condition on line 101 was always true

102 service.model_disconnected = True 

103 LOG.warning( 

104 'Lost connection to nova-conductor for reporting service ' 

105 'status.' 

106 ) 

107 except exception.ServiceNotFound: 

108 # The service may have been deleted via the API but the actual 

109 # process is still running. Provide a useful error message rather 

110 # than the noisy traceback in the generic Exception block below. 

111 LOG.error('The services table record for the %s service on ' 

112 'host %s is gone. You either need to stop this service ' 

113 'if it should be deleted or restart it to recreate the ' 

114 'record in the database.', 

115 service.service_ref.binary, service.service_ref.host) 

116 service.model_disconnected = True 

117 except Exception: 

118 # NOTE(rpodolyaka): we'd like to avoid catching of all possible 

119 # exceptions here, but otherwise it would become possible for 

120 # the state reporting thread to stop abruptly, and thus leave 

121 # the service unusable until it's restarted. 

122 LOG.exception('Unexpected error while reporting service status') 

123 # trigger the recovery log message, if this error goes away 

124 service.model_disconnected = True