~ubuntu-branches/ubuntu/natty/cloud-init/natty-proposed

« back to all changes in this revision

Viewing changes to .pc/336-rename-process-user-data-file.txt.patch/cloudinit/__init__.py

  • Committer: Package Import Robot
  • Author(s): Scott Moser
  • Date: 2011-02-19 01:16:10 UTC
  • Revision ID: package-import@ubuntu.com-20110219011610-mpyxs8b82wwk8ubw
Tags: 0.6.1-0ubuntu1
* New upstream release.
* fix for puppet configuration options (LP: #709946) [Ryan Lane]
* fix pickling of DataSource, which broke seeding.
* turn resize_rootfs default to True
* avoid mounts in DataSourceOVF if 'read' on device fails
  'mount /dev/sr0' for an empty virtual cdrom device was taking 18 seconds
* add 'manual_cache_clean' option to select manual cleaning of
  the /var/lib/cloud/instance/ link, for a data source that might
  not be present on every boot
* make DataSourceEc2 retries and timeout configurable
* add 'bootcmd' like 'runcmd' to cloud-config syntax for running things early
* move from '#opt_include' in config file format to conf_d.
  now local config files should live in /etc/cloud/cloud.cfg.d/
* move /etc/cloud/distro.cfg to /etc/cloud/cloud.cfg.d/90_dpkg.cfg
* allow /etc/hosts to be written from hosts.tmpl. which allows
  getting local-hostname into /etc/hosts (LP: #720440)
* better handle startup if there is no eth0 (LP: #714807)
* update rather than append in puppet config [Marc Cluet]
* add cloud-config for mcollective [Marc Cluet]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vi: ts=4 expandtab
2
 
#
3
 
#    Common code for the EC2 initialisation scripts in Ubuntu
4
 
#    Copyright (C) 2008-2009 Canonical Ltd
5
 
#
6
 
#    Author: Soren Hansen <soren@canonical.com>
7
 
#
8
 
#    This program is free software: you can redistribute it and/or modify
9
 
#    it under the terms of the GNU General Public License version 3, as
10
 
#    published by the Free Software Foundation.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
 
21
 
varlibdir = '/var/lib/cloud'
22
 
cur_instance_link = varlibdir + "/instance"
23
 
boot_finished = cur_instance_link + "/boot-finished"
24
 
system_config = '/etc/cloud/cloud.cfg'
25
 
seeddir = varlibdir + "/seed"
26
 
cfg_env_name = "CLOUD_CFG"
27
 
 
28
 
cfg_builtin = """
29
 
log_cfgs: [ ]
30
 
datasource_list: [ "NoCloud", "OVF", "Ec2" ]
31
 
def_log_file: /var/log/cloud-init.log
32
 
syslog_fix_perms: syslog:adm
33
 
"""
34
 
logger_name = "cloudinit"
35
 
 
36
 
pathmap = {
37
 
   "handlers" : "/handlers",
38
 
   "scripts" : "/scripts",
39
 
   "sem" : "/sem",
40
 
   "boothooks" : "/boothooks",
41
 
   "userdata_raw" : "/user-data.txt",
42
 
   "userdata" : "/user-data-raw.txt.i",
43
 
   "obj_pkl" : "/obj.pkl",
44
 
   "cloud_config" : "/cloud-config.txt",
45
 
   "datadir" : "/data",
46
 
   None : "",
47
 
}
48
 
 
49
 
parsed_cfgs = { }
50
 
 
51
 
import os
52
 
from   configobj import ConfigObj
53
 
 
54
 
import cPickle
55
 
import sys
56
 
import os.path
57
 
import errno
58
 
import pwd
59
 
import subprocess
60
 
import yaml
61
 
import util
62
 
import logging
63
 
import logging.config
64
 
import StringIO
65
 
import glob
66
 
 
67
 
class NullHandler(logging.Handler):
68
 
    def emit(self,record): pass
69
 
 
70
 
log = logging.getLogger(logger_name)
71
 
log.addHandler(NullHandler())
72
 
 
73
 
def logging_set_from_cfg_file(cfg_file=system_config):
74
 
    logging_set_from_cfg(util.get_base_cfg(cfg_file,cfg_builtin,parsed_cfgs))
75
 
 
76
 
def logging_set_from_cfg(cfg):
77
 
    log_cfgs = []
78
 
    logcfg=util.get_cfg_option_str(cfg, "log_cfg", False)
79
 
    if logcfg:
80
 
        # if there is a 'logcfg' entry in the config, respect
81
 
        # it, it is the old keyname
82
 
        log_cfgs = [ logcfg ]
83
 
    elif "log_cfgs" in cfg:
84
 
        for cfg in cfg['log_cfgs']:
85
 
            if isinstance(cfg,list):
86
 
                log_cfgs.append('\n'.join(cfg))
87
 
            else:
88
 
                log_cfgs.append()
89
 
 
90
 
    if not len(log_cfgs):
91
 
        sys.stderr.write("Warning, no logging configured\n")
92
 
        return
93
 
 
94
 
    for logcfg in log_cfgs:
95
 
        try:
96
 
            logging.config.fileConfig(StringIO.StringIO(logcfg))
97
 
            return
98
 
        except:
99
 
            pass
100
 
 
101
 
    raise Exception("no valid logging found\n")
102
 
 
103
 
 
104
 
import DataSource
105
 
import UserDataHandler
106
 
 
107
 
class CloudInit:
108
 
    cfg = None
109
 
    part_handlers = { }
110
 
    old_conffile = '/etc/ec2-init/ec2-config.cfg'
111
 
    ds_deps = [ DataSource.DEP_FILESYSTEM, DataSource.DEP_NETWORK ]
112
 
    datasource = None
113
 
 
114
 
    def __init__(self, ds_deps = None, sysconfig=system_config):
115
 
        self.part_handlers = {
116
 
            'text/x-shellscript' : self.handle_user_script,
117
 
            'text/cloud-config' : self.handle_cloud_config,
118
 
            'text/upstart-job' : self.handle_upstart_job,
119
 
            'text/part-handler' : self.handle_handler,
120
 
            'text/cloud-boothook' : self.handle_cloud_boothook
121
 
        }
122
 
        if ds_deps != None:
123
 
            self.ds_deps = ds_deps
124
 
        self.sysconfig=sysconfig
125
 
        self.cfg=self.read_cfg()
126
 
 
127
 
    def read_cfg(self):
128
 
        if self.cfg:
129
 
            return(self.cfg)
130
 
 
131
 
        try:
132
 
            conf = util.get_base_cfg(self.sysconfig,cfg_builtin, parsed_cfgs)
133
 
        except Exception as e:
134
 
            conf = get_builtin_cfg()
135
 
 
136
 
        # support reading the old ConfigObj format file and merging
137
 
        # it into the yaml dictionary
138
 
        try:
139
 
            from configobj import ConfigObj
140
 
            oldcfg = ConfigObj(self.old_conffile)
141
 
            if oldcfg is None: oldcfg = { }
142
 
            conf = util.mergedict(conf,oldcfg)
143
 
        except:
144
 
            pass
145
 
 
146
 
        return(conf)
147
 
 
148
 
    def restore_from_cache(self):
149
 
        try:
150
 
            # we try to restore from a current link and static path
151
 
            # by using the instance link, if purge_cache was called
152
 
            # the file wont exist
153
 
            cache = get_ipath_cur('obj_pkl')
154
 
            f=open(cache, "rb")
155
 
            data = cPickle.load(f)
156
 
            self.datasource = data
157
 
            return True
158
 
        except:
159
 
            return False
160
 
 
161
 
    def write_to_cache(self):
162
 
        cache = self.get_ipath("obj_pkl")
163
 
        try:
164
 
            os.makedirs(os.path.dirname(cache))
165
 
        except OSError as e:
166
 
            if e.errno != errno.EEXIST:
167
 
                return False
168
 
                
169
 
        try:
170
 
            f=open(cache, "wb")
171
 
            data = cPickle.dump(self.datasource,f)
172
 
            os.chmod(cache,0400)
173
 
            return True
174
 
        except:
175
 
            return False
176
 
        
177
 
    def get_data_source(self):
178
 
        if self.datasource is not None: return True
179
 
 
180
 
        if self.restore_from_cache():
181
 
            log.debug("restored from cache type %s" % self.datasource)
182
 
            return True
183
 
 
184
 
        cfglist=self.cfg['datasource_list']
185
 
        dslist = list_sources(cfglist, self.ds_deps)
186
 
        dsnames = map(lambda f: f.__name__, dslist)
187
 
        log.debug("searching for data source in %s" % dsnames)
188
 
        for cls in dslist:
189
 
            ds = cls.__name__
190
 
            try:
191
 
                s = cls(log)
192
 
                if s.get_data():
193
 
                    self.datasource = s
194
 
                    self.datasource_name = ds
195
 
                    log.debug("found data source %s" % ds)
196
 
                    return True
197
 
            except Exception as e:
198
 
                log.warn("get_data of %s raised %s" % (ds,e))
199
 
                util.logexc(log)
200
 
                pass
201
 
        msg = "Did not find data source. searched classes: %s" % dsnames
202
 
        log.debug(msg)
203
 
        raise DataSourceNotFoundException(msg)
204
 
 
205
 
    def set_cur_instance(self):
206
 
        try:
207
 
            os.unlink(cur_instance_link)
208
 
        except OSError as e:
209
 
            if e.errno != errno.ENOENT: raise
210
 
 
211
 
        os.symlink("./instances/%s" % self.get_instance_id(), cur_instance_link)
212
 
        idir = self.get_ipath()
213
 
        dlist = []
214
 
        for d in [ "handlers", "scripts", "sem" ]:
215
 
            dlist.append("%s/%s" % (idir, d))
216
 
            
217
 
        util.ensure_dirs(dlist)
218
 
 
219
 
    def get_userdata(self):
220
 
        return(self.datasource.get_userdata())
221
 
 
222
 
    def get_userdata_raw(self):
223
 
        return(self.datasource.get_userdata_raw())
224
 
 
225
 
    def get_instance_id(self):
226
 
        return(self.datasource.get_instance_id())
227
 
 
228
 
    def update_cache(self):
229
 
        self.write_to_cache()
230
 
        self.store_userdata()
231
 
 
232
 
    def store_userdata(self):
233
 
        util.write_file(self.get_ipath('userdata_raw'),
234
 
            self.datasource.get_userdata_raw(), 0600)
235
 
        util.write_file(self.get_ipath('userdata'),
236
 
            self.datasource.get_userdata(), 0600)
237
 
 
238
 
    def initctl_emit(self):
239
 
        cc_path = get_ipath_cur('cloud_config')
240
 
        subprocess.Popen(['initctl', 'emit', 'cloud-config',
241
 
            '%s=%s' % (cfg_env_name,cc_path)]).communicate()
242
 
 
243
 
    def sem_getpath(self,name,freq):
244
 
        if freq == 'once-per-instance':
245
 
            return("%s/%s" % (self.get_ipath("sem"),name))
246
 
        return("%s/%s.%s" % (get_cpath("sem"), name, freq))
247
 
    
248
 
    def sem_has_run(self,name,freq):
249
 
        if freq == "always": return False
250
 
        semfile = self.sem_getpath(name,freq)
251
 
        if os.path.exists(semfile):
252
 
            return True
253
 
        return False
254
 
    
255
 
    def sem_acquire(self,name,freq):
256
 
        from time import time
257
 
        semfile = self.sem_getpath(name,freq)
258
 
    
259
 
        try:
260
 
            os.makedirs(os.path.dirname(semfile))
261
 
        except OSError as e:
262
 
            if e.errno != errno.EEXIST:
263
 
                raise e
264
 
    
265
 
        if os.path.exists(semfile) and freq != "always":
266
 
            return False
267
 
    
268
 
        # race condition
269
 
        try:
270
 
            f = open(semfile,"w")
271
 
            f.write("%s\n" % str(time()))
272
 
            f.close()
273
 
        except:
274
 
            return(False)
275
 
        return(True)
276
 
    
277
 
    def sem_clear(self,name,freq):
278
 
        semfile = self.sem_getpath(name,freq)
279
 
        try:
280
 
            os.unlink(semfile)
281
 
        except OSError as e:
282
 
            if e.errno != errno.ENOENT:
283
 
                return False
284
 
            
285
 
        return True
286
 
 
287
 
    # acquire lock on 'name' for given 'freq'
288
 
    # if that does not exist, then call 'func' with given 'args'
289
 
    # if 'clear_on_fail' is True and func throws an exception
290
 
    #  then remove the lock (so it would run again)
291
 
    def sem_and_run(self,semname,freq,func,args=[],clear_on_fail=False):
292
 
        if self.sem_has_run(semname,freq):
293
 
            log.debug("%s already ran %s", semname, freq)
294
 
            return
295
 
        try:
296
 
            if not self.sem_acquire(semname,freq):
297
 
                raise Exception("Failed to acquire lock on %s" % semname)
298
 
 
299
 
            func(*args)
300
 
        except:
301
 
            if clear_on_fail:
302
 
                self.sem_clear(semname,freq)
303
 
            raise
304
 
 
305
 
    # get_ipath : get the instance path for a name in pathmap
306
 
    # (/var/lib/cloud/instances/<instance>/name)<name>)
307
 
    def get_ipath(self, name=None):
308
 
        return("%s/instances/%s%s" 
309
 
               % (varlibdir,self.get_instance_id(), pathmap[name]))
310
 
 
311
 
    def consume_userdata(self):
312
 
        self.get_userdata()
313
 
        data = self
314
 
 
315
 
        cdir = get_cpath("handlers")
316
 
        idir = self.get_ipath("handlers")
317
 
 
318
 
        # add the path to the plugins dir to the top of our list for import
319
 
        # instance dir should be read before cloud-dir
320
 
        sys.path.insert(0,cdir)
321
 
        sys.path.insert(0,idir)
322
 
 
323
 
        # add handlers in cdir
324
 
        for fname in glob.glob("%s/*.py" % cdir):
325
 
            if not os.path.isfile(fname): continue
326
 
            modname = os.path.basename(fname)[0:-3]
327
 
            try:
328
 
                mod = __import__(modname)
329
 
                lister = getattr(mod, "list_types")
330
 
                handler = getattr(mod, "handle_part")
331
 
                mtypes = lister()
332
 
                for mtype in mtypes:
333
 
                    self.part_handlers[mtype]=handler
334
 
                log.debug("added handler for [%s] from %s" % (mtypes,fname))
335
 
            except:
336
 
                log.warn("failed to initialize handler in %s" % fname)
337
 
                util.logexc(log)
338
 
       
339
 
        # give callbacks opportunity to initialize
340
 
        for ctype, func in self.part_handlers.items():
341
 
            func(data, "__begin__",None,None)
342
 
        UserDataHandler.walk_userdata(self.get_userdata(),
343
 
            self.part_handlers, data)
344
 
 
345
 
        # give callbacks opportunity to finalize
346
 
        for ctype, func in self.part_handlers.items():
347
 
            func(data,"__end__",None,None)
348
 
 
349
 
    def handle_handler(self,data,ctype,filename,payload):
350
 
        if ctype == "__end__": return
351
 
        if ctype == "__begin__" :
352
 
            self.handlercount = 0
353
 
            return
354
 
 
355
 
        self.handlercount=self.handlercount+1
356
 
 
357
 
        # write content to instance's handlerdir
358
 
        handlerdir = self.get_ipath("handler")
359
 
        modname  = 'part-handler-%03d' % self.handlercount
360
 
        modfname = modname + ".py"
361
 
        util.write_file("%s/%s" % (handlerdir,modfname), payload, 0600)
362
 
 
363
 
        try:
364
 
            mod = __import__(modname)
365
 
            lister = getattr(mod, "list_types")
366
 
            handler = getattr(mod, "handle_part")
367
 
        except:
368
 
            import traceback
369
 
            traceback.print_exc(file=sys.stderr)
370
 
            return
371
 
 
372
 
        # - call it with '__begin__'
373
 
        handler(data, "__begin__", None, None)
374
 
 
375
 
        # - add it self.part_handlers
376
 
        for mtype in lister():
377
 
            self.part_handlers[mtype]=handler
378
 
 
379
 
    def handle_user_script(self,data,ctype,filename,payload):
380
 
        if ctype == "__end__": return
381
 
        if ctype == "__begin__":
382
 
            # maybe delete existing things here
383
 
            return
384
 
 
385
 
        filename=filename.replace(os.sep,'_')
386
 
        scriptsdir = get_ipath_cur('scripts')
387
 
        util.write_file("%s/%s/%s" % 
388
 
            (scriptsdir,self.get_instance_id(),filename), payload, 0700)
389
 
 
390
 
    def handle_upstart_job(self,data,ctype,filename,payload):
391
 
        if ctype == "__end__" or ctype == "__begin__": return
392
 
        if not filename.endswith(".conf"):
393
 
            filename=filename+".conf"
394
 
 
395
 
        util.write_file("%s/%s" % ("/etc/init",filename), payload, 0644)
396
 
 
397
 
    def handle_cloud_config(self,data,ctype,filename,payload):
398
 
        if ctype == "__begin__":
399
 
            self.cloud_config_str=""
400
 
            return
401
 
        if ctype == "__end__":
402
 
            cloud_config = self.get_ipath("cloud_config")
403
 
            util.write_file(cloud_config, self.cloud_config_str, 0600)
404
 
 
405
 
            ## this could merge the cloud config with the system config
406
 
            ## for now, not doing this as it seems somewhat circular
407
 
            ## as CloudConfig does that also, merging it with this cfg
408
 
            ##
409
 
            # ccfg = yaml.load(self.cloud_config_str)
410
 
            # if ccfg is None: ccfg = { }
411
 
            # self.cfg = util.mergedict(ccfg, self.cfg)
412
 
 
413
 
            return
414
 
 
415
 
        self.cloud_config_str+="\n#%s\n%s" % (filename,payload)
416
 
 
417
 
    def handle_cloud_boothook(self,data,ctype,filename,payload):
418
 
        if ctype == "__end__": return
419
 
        if ctype == "__begin__": return
420
 
 
421
 
        filename=filename.replace(os.sep,'_')
422
 
        prefix="#cloud-boothook"
423
 
        dos=False
424
 
        start = 0
425
 
        if payload.startswith(prefix):
426
 
            start = len(prefix)
427
 
            if payload[start] == '\r':
428
 
                start=start+1
429
 
                dos = True
430
 
        else:
431
 
            if payload.find('\r\n',0,100) >= 0:
432
 
                dos = True
433
 
    
434
 
        if dos:
435
 
            payload=payload[start:].replace('\r\n','\n')
436
 
        elif start != 0:
437
 
            payload=payload[start:]
438
 
    
439
 
        boothooks_dir = self.get_ipath("boothooks")
440
 
        filepath = "%s/%s" % (boothooks_dir,filename)
441
 
        util.write_file(filepath, payload, 0700)
442
 
        try:
443
 
            env=os.environ.copy()
444
 
            env['INSTANCE_ID']= self.datasource.get_instance_id()
445
 
            ret = subprocess.check_call([filepath], env=env)
446
 
        except subprocess.CalledProcessError as e:
447
 
            log.error("boothooks script %s returned %i" %
448
 
                (filepath,e.returncode))
449
 
        except Exception as e:
450
 
            log.error("boothooks unknown exception %s when running %s" %
451
 
                (e,filepath))
452
 
 
453
 
    def get_public_ssh_keys(self):
454
 
        return(self.datasource.get_public_ssh_keys())
455
 
 
456
 
    def get_locale(self):
457
 
        return(self.datasource.get_locale())
458
 
 
459
 
    def get_mirror(self):
460
 
        return(self.datasource.get_local_mirror())
461
 
 
462
 
    def get_hostname(self):
463
 
        return(self.datasource.get_hostname())
464
 
 
465
 
    def device_name_to_device(self,name):
466
 
        return(self.datasource.device_name_to_device(name))
467
 
 
468
 
    # I really don't know if this should be here or not, but
469
 
    # I needed it in cc_update_hostname, where that code had a valid 'cloud'
470
 
    # reference, but did not have a cloudinit handle
471
 
    # (ie, no cloudinit.get_cpath())
472
 
    def get_cpath(self,name=None):
473
 
        return(get_cpath(name))
474
 
 
475
 
 
476
 
def initfs():
477
 
    subds = [ 'scripts/per-instance', 'scripts/per-once', 'scripts/per-boot',
478
 
              'seed', 'instances', 'handlers', 'sem', 'data' ]
479
 
    dlist = [ ]
480
 
    for subd in subds:
481
 
        dlist.append("%s/%s" % (varlibdir, subd))
482
 
    util.ensure_dirs(dlist)
483
 
 
484
 
    cfg = util.get_base_cfg(system_config,cfg_builtin,parsed_cfgs)
485
 
    log_file = util.get_cfg_option_str(cfg, 'def_log_file', None)
486
 
    perms = util.get_cfg_option_str(cfg, 'syslog_fix_perms', None)
487
 
    if log_file:
488
 
        fp = open(log_file,"ab")
489
 
        fp.close()
490
 
    if log_file and perms:
491
 
        (u,g) = perms.split(':',1)
492
 
        if u == "-1" or u == "None": u = None
493
 
        if g == "-1" or g == "None": g = None
494
 
        util.chownbyname(log_file, u, g)
495
 
 
496
 
def purge_cache():
497
 
    rmlist = ( boot_finished , cur_instance_link )
498
 
    for f in rmlist:
499
 
        try:
500
 
            os.unlink(f)
501
 
        except OSError as e:
502
 
            if e.errno == errno.ENOENT: continue
503
 
            return(False)
504
 
        except:
505
 
            return(False)
506
 
    return(True)
507
 
 
508
 
# get_ipath_cur: get the current instance path for an item
509
 
def get_ipath_cur(name=None):
510
 
    return("%s/instance/%s" % (varlibdir, pathmap[name]))
511
 
 
512
 
# get_cpath : get the "clouddir" (/var/lib/cloud/<name>)
513
 
# for a name in dirmap
514
 
def get_cpath(name=None):
515
 
    return("%s%s" % (varlibdir, pathmap[name]))
516
 
 
517
 
def get_base_cfg(cfg_path=None):
518
 
    if cfg_path is None: cfg_path = system_config
519
 
    return(util.get_base_cfg(cfg_path,cfg_builtin,parsed_cfgs))
520
 
 
521
 
def get_builtin_cfg():
522
 
    return(yaml.load(cfg_builtin))
523
 
 
524
 
class DataSourceNotFoundException(Exception):
525
 
    pass
526
 
 
527
 
def list_sources(cfg_list, depends):
528
 
    return(DataSource.list_sources(cfg_list,depends, ["cloudinit", "" ]))