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
« 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.
16from oslo_log import log as logging
17import oslo_messaging as messaging
18from oslo_utils import timeutils
20import nova.conf
21from nova import exception
22from nova.i18n import _
23from nova.servicegroup import api
24from nova.servicegroup.drivers import base
27CONF = nova.conf.CONF
29LOG = logging.getLogger(__name__)
32class DbDriver(base.Driver):
34 def __init__(self, *args, **kwargs):
35 self.service_down_time = CONF.service_down_time
37 def join(self, member, group, service=None):
38 """Add a new member to a service group.
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)
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
83 def updated_time(self, service_ref):
84 """Get the updated time from db"""
85 return service_ref['updated_at']
87 def _report_state(self, service):
88 """Update the state of this service in the datastore."""
90 try:
91 service.service_ref.report_count += 1
92 service.service_ref.save()
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