Skip to content

Commit 54346d3

Browse files
committed
initial dump of "sans-cloud" code (DataSourceNoCloud)
The new classes 'DataSourceNoCloud' and 'DataSourceNoCloudNet' implement a way to get data from the filesystem, or (very minimal) data from the kernel command line. This allows the user to seed data to these sources. There are now 2 "cloud-init" jobs, cloud-init-local that runs on mounted MOUNTPOINT=/ and 'cloud-init' that runs on start on (mounted MOUNTPOINT=/ and net-device-up IFACE=eth0 and stopped cloud-init-local ) The idea is that cloud-init-local can actually function without network. The last thing in this commit is "uncloud-init". This tool can be invoked as 'init=/usr/lib/cloud-init/uncloud-init' It will "uncloudify" things in the image, generally making it easier to use for a simpler environment, and then it will exec /sbin/init.
1 parent a433574 commit 54346d3

File tree

11 files changed

+459
-53
lines changed

11 files changed

+459
-53
lines changed

cloud-init.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ def warn(str):
3030
sys.stderr.write(str)
3131

3232
def main():
33+
cmds = ( "start", "start-local" )
34+
cmd = ""
35+
if len(sys.argv) > 1:
36+
cmd = sys.argv[1]
37+
38+
if not cmd in cmds:
39+
sys.stderr.write("bad command %s. use one of %s\n" % (cmd, cmds))
40+
sys.exit(1)
41+
3342
now = time.strftime("%a, %d %b %Y %H:%M:%S %z")
3443
try:
3544
uptimef=open("/proc/uptime")
@@ -39,29 +48,39 @@ def main():
3948
warn("unable to open /proc/uptime\n")
4049
uptime = "na"
4150

42-
msg = "cloud-init running: %s. up %s seconds" % (now, uptime)
51+
msg = "cloud-init %s running: %s. up %s seconds" % (cmd, now, uptime)
4352
sys.stderr.write(msg + "\n")
4453
sys.stderr.flush()
4554

55+
source_type = "all"
56+
if cmd == "start-local":
57+
source_type = "local"
58+
4659
cloudinit.logging_set_from_cfg_file()
4760
log = logging.getLogger()
4861
log.info(msg)
4962

5063
# cache is not instance specific, so it has to be purged
51-
cloudinit.purge_cache()
64+
# but we want 'start' to benefit from a cache if
65+
# a previous start-local populated one
66+
if cmd == "start-local":
67+
cloudinit.purge_cache()
5268

53-
cloud = cloudinit.CloudInit()
69+
cloud = cloudinit.CloudInit(source_type=source_type)
5470

5571
try:
5672
cloud.get_data_source()
57-
except Exception as e:
58-
print e
59-
sys.stderr.write("Failed to get instance data\n")
73+
except cloudinit.DataSourceNotFoundException as e:
74+
sys.stderr.write("no instance data found in %s\n" % cmd)
6075
sys.exit(1)
6176

6277
# store the metadata
6378
cloud.update_cache()
6479

80+
msg = "found data source: %s" % cloud.datasource
81+
sys.stderr.write(msg + "\n")
82+
log.debug(msg)
83+
6584
# parse the user data (ec2-run-userdata.py)
6685
try:
6786
cloud.sem_and_run("consume_userdata", "once-per-instance",

cloudinit/DataSource.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,20 @@ def get_userdata_raw(self):
3636
return(self.userdata_raw)
3737

3838
def get_public_ssh_keys(self):
39-
return([])
39+
keys = []
40+
if not self.metadata.has_key('public-keys'): return([])
41+
for keyname, klist in self.metadata['public-keys'].items():
42+
# lp:506332 uec metadata service responds with
43+
# data that makes boto populate a string for 'klist' rather
44+
# than a list.
45+
if isinstance(klist,str):
46+
klist = [ klist ]
47+
for pkey in klist:
48+
# there is an empty string at the end of the keylist, trim it
49+
if pkey:
50+
keys.append(pkey)
51+
52+
return(keys)
4053

4154
def device_name_to_device(self, name):
4255
# translate a 'name' to a device
@@ -45,3 +58,29 @@ def device_name_to_device(self, name):
4558
# ephemeral0: sdb
4659
# and return 'sdb' for input 'ephemeral0'
4760
return(None)
61+
62+
def get_locale(self):
63+
return('en_US.UTF-8')
64+
65+
def get_local_mirror(self):
66+
return('http://archive.ubuntu.com/ubuntu/')
67+
68+
def get_instance_id(self):
69+
if 'instance-id' not in self.metadata:
70+
return "ubuntuhost"
71+
return(self.metadata['instance-id'])
72+
73+
def get_hostname(self):
74+
if not 'local-hostname' in self.metadata:
75+
return None
76+
77+
toks = self.metadata['local-hostname'].split('.')
78+
# if there is an ipv4 address in 'local-hostname', then
79+
# make up a hostname (LP: #475354)
80+
if len(toks) == 4:
81+
try:
82+
r = filter(lambda x: int(x) < 256 and x > 0, toks)
83+
if len(r) == 4:
84+
return("ip-%s" % '-'.join(r))
85+
except: pass
86+
return toks[0]

cloudinit/DataSourceEc2.py

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
import DataSource
2020

2121
import cloudinit
22+
import cloudinit.util as util
2223
import socket
2324
import urllib2
2425
import time
2526
import sys
2627
import boto_utils
2728
import os.path
29+
import errno
2830

2931
class DataSourceEc2(DataSource.DataSource):
3032
api_ver = '2009-04-04'
@@ -39,21 +41,20 @@ class DataSourceEc2(DataSource.DataSource):
3941
def __init__(self):
4042
pass
4143

44+
def __str__(self):
45+
return("DataSourceEc2")
46+
4247
def get_data(self):
4348
try:
44-
udf = open(self.cachedir + "/user-data.raw")
45-
self.userdata_raw = udf.read()
46-
udf.close()
47-
48-
mdf = open(self.cachedir + "/meta-data.raw")
49-
data = mdf.read()
50-
self.metadata = eval(data)
51-
mdf.close()
52-
49+
(md,ud) = util.read_seeded(self.cachedir + "/")
50+
self.userdata_raw = ud
51+
self.metadata = md
5352
cloudinit.log.debug("using seeded ec2 cache data in %s" % self.cachedir)
5453
return True
55-
except:
56-
pass
54+
except OSError, e:
55+
if e.errno != errno.ENOENT:
56+
cloudinit.log.warn("unexpected error from preseeded data")
57+
raise
5758

5859
try:
5960
if not self.wait_for_metadata_service():
@@ -81,18 +82,6 @@ def get_locale(self):
8182
else:
8283
return(self.location_locale_map["default"])
8384

84-
def get_hostname(self):
85-
toks = self.metadata['local-hostname'].split('.')
86-
# if there is an ipv4 address in 'local-hostname', then
87-
# make up a hostname (LP: #475354)
88-
if len(toks) == 4:
89-
try:
90-
r = filter(lambda x: int(x) < 256 and x > 0, toks)
91-
if len(r) == 4:
92-
return("ip-%s" % '-'.join(r))
93-
except: pass
94-
return toks[0]
95-
9685
def get_mirror_from_availability_zone(self, availability_zone = None):
9786
# availability is like 'us-west-1b' or 'eu-west-1a'
9887
if availability_zone == None:
@@ -138,22 +127,6 @@ def wait_for_metadata_service(self, sleeps = 100):
138127
int(time.time()-starttime))
139128
return False
140129

141-
def get_public_ssh_keys(self):
142-
keys = []
143-
if not self.metadata.has_key('public-keys'): return([])
144-
for keyname, klist in self.metadata['public-keys'].items():
145-
# lp:506332 uec metadata service responds with
146-
# data that makes boto populate a string for 'klist' rather
147-
# than a list.
148-
if isinstance(klist,str):
149-
klist = [ klist ]
150-
for pkey in klist:
151-
# there is an empty string at the end of the keylist, trim it
152-
if pkey:
153-
keys.append(pkey)
154-
155-
return(keys)
156-
157130
def device_name_to_device(self, name):
158131
# consult metadata service, that has
159132
# ephemeral0: sdb

cloudinit/DataSourceNoCloud.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# vi: ts=4 expandtab
2+
#
3+
# Copyright (C) 2009-2010 Canonical Ltd.
4+
#
5+
# Author: Scott Moser <scott.moser@canonical.com>
6+
#
7+
# This program is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License version 3, as
9+
# published by the Free Software Foundation.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
19+
import DataSource
20+
21+
import cloudinit
22+
import cloudinit.util as util
23+
import sys
24+
import os.path
25+
import os
26+
import errno
27+
28+
class DataSourceNoCloud(DataSource.DataSource):
29+
metadata = None
30+
userdata = None
31+
userdata_raw = None
32+
supported_seed_starts = ( "/" , "file://" )
33+
seed = None
34+
cmdline_id = "ds=nocloud"
35+
seeddir = cloudinit.cachedir + '/nocloud'
36+
37+
def __init__(self):
38+
pass
39+
40+
def __str__(self):
41+
mstr="DataSourceNoCloud"
42+
mstr = mstr + " [seed=%s]" % self.seed
43+
return(mstr)
44+
45+
def get_data(self):
46+
defaults = {
47+
"local-hostname" : "ubuntuhost",
48+
"instance-id" : "nocloud"
49+
}
50+
51+
md = { }
52+
ud = ""
53+
54+
try:
55+
# parse the kernel command line, getting data passed in
56+
md = parse_cmdline_data(self.cmdline_id)
57+
except:
58+
util.logexc(cloudinit.log,util.WARN)
59+
return False
60+
61+
# check to see if the seeddir has data.
62+
try:
63+
(seeddir_md,ud) = util.read_seeded(self.seeddir + "/")
64+
self.metadata = md
65+
md = util.mergedict(md,seeddir_md)
66+
cloudinit.log.debug("using seeded cache data in %s" % self.seeddir)
67+
except OSError, e:
68+
if e.errno != errno.ENOENT:
69+
util.logexc(cloudinit.log,util.WARN)
70+
raise
71+
72+
# there was no indication on kernel cmdline or data
73+
# in the seeddir suggesting this handler should be used.
74+
if md is None:
75+
return False
76+
77+
# the special argument "seedfrom" indicates we should
78+
# attempt to seed the userdata / metadata from its value
79+
if "seedfrom" in md:
80+
seedfrom = md["seedfrom"]
81+
found = False
82+
for proto in self.supported_seed_starts:
83+
if seedfrom.startswith(proto):
84+
found=proto
85+
break
86+
if not found:
87+
cloudinit.log.debug("seed from %s not supported by %s" %
88+
(seedfrom, self.__class__))
89+
return False
90+
91+
# this could throw errors, but the user told us to do it
92+
# so if errors are raised, let them raise
93+
(md_seed,ud) = util.read_seeded(seedfrom)
94+
cloudinit.log.debug("using seeded cache data from %s" % seedfrom)
95+
96+
# values in the command line override those from the seed
97+
md = util.mergedict(md,md_seed)
98+
self.seed = seedfrom
99+
100+
md = util.mergedict(md,defaults)
101+
self.metadata = md;
102+
self.userdata_raw = ud
103+
return True
104+
105+
# returns a dictionary of key/val or None if cmdline did not have data
106+
# example cmdline:
107+
# root=LABEL=uec-rootfs ro ds=nocloud
108+
def parse_cmdline_data(ds_id,cmdline=None):
109+
if cmdline is None:
110+
if 'DEBUG_PROC_CMDLINE' in os.environ:
111+
cmdline = os.environ["DEBUG_PROC_CMDLINE"]
112+
else:
113+
cmdfp = open("/proc/cmdline")
114+
cmdline = cmdfp.read().strip()
115+
cmdfp.close()
116+
cmdline = " %s " % cmdline.lower()
117+
118+
if not ( " %s " % ds_id in cmdline or " %s;" % ds_id in cmdline ):
119+
return None
120+
121+
argline=""
122+
# cmdline can contain:
123+
# ds=nocloud[;key=val;key=val]
124+
for tok in cmdline.split():
125+
if tok.startswith(ds_id): argline=tok.split("=",1)
126+
127+
# argline array is now 'nocloud' followed optionally by
128+
# a ';' and then key=value pairs also terminated with ';'
129+
tmp=argline[1].split(";")
130+
if len(tmp) > 1:
131+
kvpairs=tmp[1:]
132+
else:
133+
kvpairs=()
134+
135+
# short2long mapping to save cmdline typing
136+
s2l = { "h" : "local-hostname", "i" : "instance-id", "s" : "seedfrom" }
137+
cfg = { }
138+
for item in kvpairs:
139+
try:
140+
(k,v) = item.split("=",1)
141+
except:
142+
k=item
143+
v=None
144+
if k in s2l: k=s2l[k]
145+
cfg[k]=v
146+
147+
return(cfg)
148+
149+
class DataSourceNoCloudNet(DataSourceNoCloud):
150+
cmdline_id = "ds=nocloud-net"
151+
supported_seed_starts = ( "http://", "https://", "ftp://" )
152+
seeddir = cloudinit.cachedir + '/nocloud-net'

0 commit comments

Comments
 (0)