Skip to content

Commit 0964b42

Browse files
committed
quickly check to see if the previous instance id is still valid
This adds a check in cloud-init to see if the existing (cached) datasource is still valid. It relies on support from the Datasource to implement 'check_instance_id'. That method should quickly determine (if possible) if the instance id found in the datasource is still valid. This means that we can still notice new instance ids without depending on a network datasource on every boot. I've also implemented check_instance_id for the superclass and for 3 classes: DataSourceAzure (check dmi data) DataSourceOpenstack (check dmi data) DataSourceNocloud (check the seeded data or kernel command line) LP: #1553815
1 parent 1b91b7c commit 0964b42

File tree

7 files changed

+85
-19
lines changed

7 files changed

+85
-19
lines changed

ChangeLog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@
9292
- doc: mention label for nocloud datasource must be 'cidata' [Peter Hurley]
9393
- ssh_pwauth: fix module to support 'unchanged' and match behavior
9494
described in documentation [Chris Cosby]
95+
- quickly check to see if the previous instance id is still valid to
96+
avoid dependency on network metadata service on every boot (LP: #1553815)
9597

9698
0.7.6:
9799
- open 0.7.6

bin/cloud-init

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def main_init(name, args):
212212
# Stage 4
213213
path_helper = init.paths
214214
if not args.local:
215+
existing = "trust"
215216
sys.stderr.write("%s\n" % (netinfo.debug_info()))
216217
LOG.debug(("Checking to see if files that we need already"
217218
" exist from a previous run that would allow us"
@@ -236,21 +237,17 @@ def main_init(name, args):
236237
LOG.debug("Execution continuing, no previous run detected that"
237238
" would allow us to stop early.")
238239
else:
239-
# The cache is not instance specific, so it has to be purged
240-
# but we want 'start' to benefit from a cache if
241-
# a previous start-local populated one...
242-
manual_clean = util.get_cfg_option_bool(init.cfg,
243-
'manual_cache_clean', False)
244-
if manual_clean:
245-
LOG.debug("Not purging instance link, manual cleaning enabled")
246-
init.purge_cache(False)
247-
else:
248-
init.purge_cache()
240+
existing = "check"
241+
if util.get_cfg_option_bool(init.cfg, 'manual_cache_clean', False):
242+
existing = "trust"
243+
244+
init.purge_cache()
249245
# Delete the non-net file as well
250246
util.del_file(os.path.join(path_helper.get_cpath("data"), "no-net"))
247+
251248
# Stage 5
252249
try:
253-
init.fetch()
250+
init.fetch(existing=existing)
254251
except sources.DataSourceNotFoundException:
255252
# In the case of 'cloud-init init' without '--local' it is a bit
256253
# more likely that the user would consider it failure if nothing was

cloudinit/sources/DataSourceAzure.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ def device_name_to_device(self, name):
254254
def get_config_obj(self):
255255
return self.cfg
256256

257+
def check_instance_id(self):
258+
# quickly (local check only) if self.instance_id is still valid
259+
return sources.instance_id_matches_system_uuid(self.get_instance_id())
260+
257261

258262
def count_files(mp):
259263
return len(fnmatch.filter(os.listdir(mp), '*[!cdrom]*'))

cloudinit/sources/DataSourceNoCloud.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,41 @@ def _pp2d_callback(mp, data):
197197
mydata['meta-data']['dsmode'])
198198
return False
199199

200+
def check_instance_id(self):
201+
# quickly (local check only) if self.instance_id is still valid
202+
# we check kernel command line or files.
203+
current = self.get_instance_id()
204+
if not current:
205+
return None
206+
207+
quick_id = _quick_read_instance_id(cmdline_id=self.cmdline_id,
208+
dirs=[self.seed_dir])
209+
if not quick_id:
210+
return None
211+
return quick_id == current
212+
213+
214+
def _quick_read_instance_id(cmdline_id, dirs=None):
215+
if dirs is None:
216+
dirs = []
217+
218+
iid_key = 'instance-id'
219+
if cmdline_id is None:
220+
fill = {}
221+
if parse_cmdline_data(cmdline_id, fill) and iid_key in fill:
222+
return fill[iid_key]
223+
224+
for d in dirs:
225+
try:
226+
data = util.pathprefix2dict(d, required=['meta-data'])
227+
md = util.load_yaml(data['meta-data'])
228+
if iid_key in md:
229+
return md[iid_key]
230+
except ValueError:
231+
pass
232+
233+
return None
234+
200235

201236
# Returns true or false indicating if cmdline indicated
202237
# that this module should be used

cloudinit/sources/DataSourceOpenStack.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ def get_data(self):
150150

151151
return True
152152

153+
def check_instance_id(self):
154+
# quickly (local check only) if self.instance_id is still valid
155+
return sources.instance_id_matches_system_uuid(self.get_instance_id())
156+
153157

154158
def read_metadata_service(base_url, ssl_details=None):
155159
reader = openstack.MetadataReader(base_url, ssl_details=ssl_details)

cloudinit/sources/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ def get_hostname(self, fqdn=False, resolve_ip=False):
217217
def get_package_mirror_info(self):
218218
return self.distro.get_package_mirror_info(data_source=self)
219219

220+
def check_instance_id(self):
221+
# quickly (local check only) if self.instance_id is still
222+
return False
223+
220224

221225
def normalize_pubkey_data(pubkey_data):
222226
keys = []
@@ -299,6 +303,18 @@ def list_sources(cfg_list, depends, pkg_list):
299303
return src_list
300304

301305

306+
def instance_id_matches_system_uuid(instance_id, field='system-uuid'):
307+
# quickly (local check only) if self.instance_id is still valid
308+
# we check kernel command line or files.
309+
if not instance_id:
310+
return False
311+
312+
dmi_value = util.read_dmi_data(field)
313+
if not dmi_value:
314+
return False
315+
return instance_id.lower() == dmi_value.lower()
316+
317+
302318
# 'depends' is a list of dependencies (DEP_FILESYSTEM)
303319
# ds_list is a list of 2 item lists
304320
# ds_list = [

cloudinit/stages.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def _initial_subdirs(self):
140140
]
141141
return initial_dirs
142142

143-
def purge_cache(self, rm_instance_lnk=True):
143+
def purge_cache(self, rm_instance_lnk=False):
144144
rm_list = []
145145
rm_list.append(self.paths.boot_finished)
146146
if rm_instance_lnk:
@@ -238,21 +238,29 @@ def _get_datasources(self):
238238
cfg_list = self.cfg.get('datasource_list') or []
239239
return (cfg_list, pkg_list)
240240

241-
def _get_data_source(self):
241+
def _get_data_source(self, existing):
242242
if self.datasource is not NULL_DATA_SOURCE:
243243
return self.datasource
244244

245245
with events.ReportEventStack(
246246
name="check-cache",
247-
description="attempting to read from cache",
247+
description="attempting to read from cache [%s]" % existing,
248248
parent=self.reporter) as myrep:
249249
ds = self._restore_from_cache()
250-
if ds:
251-
LOG.debug("Restored from cache, datasource: %s", ds)
252-
myrep.description = "restored from cache"
250+
if ds and existing == "trust":
251+
myrep.description = "restored from cache: %s" % ds
252+
elif ds and existing == "check":
253+
if hasattr(ds, 'check_instance_id') and ds.check_instance_id():
254+
myrep.description = "restored from checked cache: %s" % ds
255+
else:
256+
myrep.description = "cache invalid in datasource: %s" % ds
257+
ds = None
253258
else:
254259
myrep.description = "no cache found"
260+
LOG.debug(myrep.description)
261+
255262
if not ds:
263+
util.del_file(self.paths.instance_link)
256264
(cfg_list, pkg_list) = self._get_datasources()
257265
# Deep copy so that user-data handlers can not modify
258266
# (which will affect user-data handlers down the line...)
@@ -332,8 +340,8 @@ def _reflect_cur_instance(self):
332340
self._reset()
333341
return iid
334342

335-
def fetch(self):
336-
return self._get_data_source()
343+
def fetch(self, existing="check"):
344+
return self._get_data_source(existing=existing)
337345

338346
def instancify(self):
339347
return self._reflect_cur_instance()

0 commit comments

Comments
 (0)