1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
# Copyright (C) 2012 Canonical Ltd.
# Copyright (C) 2012 Cosmin Luta
# Copyright (C) 2012 Yahoo! Inc.
# Copyright (C) 2012 Gerard Dethier
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
#
# Author: Cosmin Luta <q4break@gmail.com>
# Author: Scott Moser <scott.moser@canonical.com>
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
# Author: Gerard Dethier <g.dethier@gmail.com>
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
#
# This file is part of cloud-init. See LICENSE file for license information.
import os
import time
from socket import gaierror, getaddrinfo, inet_ntoa
from struct import pack
from cloudinit import log as logging
from cloudinit import sources, subp
from cloudinit import url_helper as uhelp
from cloudinit import util
from cloudinit.net import dhcp
from cloudinit.sources.helpers import ec2
LOG = logging.getLogger(__name__)
class CloudStackPasswordServerClient:
"""
Implements password fetching from the CloudStack password server.
http://cloudstack-administration.readthedocs.org/
en/latest/templates.html#adding-password-management-to-your-templates
has documentation about the system. This implementation is following that
found at
https://github.com/shankerbalan/cloudstack-scripts/
blob/master/cloud-set-guest-password-debian
"""
def __init__(self, virtual_router_address):
self.virtual_router_address = virtual_router_address
def _do_request(self, domu_request):
# The password server was in the past, a broken HTTP server, but is now
# fixed. wget handles this seamlessly, so it's easier to shell out to
# that rather than write our own handling code.
output, _ = subp.subp(
[
"wget",
"--quiet",
"--tries",
"3",
"--timeout",
"20",
"--output-document",
"-",
"--header",
"DomU_Request: {0}".format(domu_request),
"{0}:8080".format(self.virtual_router_address),
]
)
return output.strip()
def get_password(self):
password = self._do_request("send_my_password")
if password in ["", "saved_password"]:
return None
if password == "bad_request":
raise RuntimeError("Error when attempting to fetch root password.")
self._do_request("saved_password")
return password
class DataSourceCloudStack(sources.DataSource):
dsname = "CloudStack"
# Setup read_url parameters per get_url_params.
url_max_wait = 120
url_timeout = 50
def __init__(self, sys_cfg, distro, paths):
sources.DataSource.__init__(self, sys_cfg, distro, paths)
self.seed_dir = os.path.join(paths.seed_dir, "cs")
# Cloudstack has its metadata/userdata URLs located at
# http://<virtual-router-ip>/latest/
self.api_ver = "latest"
self.vr_addr = get_vr_address()
if not self.vr_addr:
raise RuntimeError("No virtual router found!")
self.metadata_address = "http://%s/" % (self.vr_addr,)
self.cfg = {}
def wait_for_metadata_service(self):
url_params = self.get_url_params()
if url_params.max_wait_seconds <= 0:
return False
urls = [
uhelp.combine_url(
self.metadata_address, "latest/meta-data/instance-id"
)
]
start_time = time.time()
url, _response = uhelp.wait_for_url(
urls=urls,
max_wait=url_params.max_wait_seconds,
timeout=url_params.timeout_seconds,
status_cb=LOG.warning,
)
if url:
LOG.debug("Using metadata source: '%s'", url)
else:
LOG.critical(
"Giving up on waiting for the metadata from %s"
" after %s seconds",
urls,
int(time.time() - start_time),
)
return bool(url)
def get_config_obj(self):
return self.cfg
def _get_data(self):
seed_ret = {}
if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):
self.userdata_raw = seed_ret["user-data"]
self.metadata = seed_ret["meta-data"]
LOG.debug("Using seeded cloudstack data from: %s", self.seed_dir)
return True
try:
if not self.wait_for_metadata_service():
return False
start_time = time.time()
self.userdata_raw = ec2.get_instance_userdata(
self.api_ver, self.metadata_address
)
self.metadata = ec2.get_instance_metadata(
self.api_ver, self.metadata_address
)
LOG.debug(
"Crawl of metadata service took %s seconds",
int(time.time() - start_time),
)
password_client = CloudStackPasswordServerClient(self.vr_addr)
try:
set_password = password_client.get_password()
except Exception:
util.logexc(
LOG,
"Failed to fetch password from virtual router %s",
self.vr_addr,
)
else:
if set_password:
self.cfg = {
"ssh_pwauth": True,
"password": set_password,
"chpasswd": {
"expire": False,
},
}
return True
except Exception:
util.logexc(
LOG,
"Failed fetching from metadata service %s",
self.metadata_address,
)
return False
def get_instance_id(self):
return self.metadata["instance-id"]
@property
def availability_zone(self):
return self.metadata["availability-zone"]
def get_data_server():
# Returns the metadataserver from dns
try:
addrinfo = getaddrinfo("data-server", 80)
except gaierror:
LOG.debug("DNS Entry data-server not found")
return None
else:
return addrinfo[0][4][0] # return IP
def get_default_gateway():
# Returns the default gateway ip address in the dotted format.
lines = util.load_file("/proc/net/route").splitlines()
for line in lines:
items = line.split("\t")
if items[1] == "00000000":
# Found the default route, get the gateway
gw = inet_ntoa(pack("<L", int(items[2], 16)))
LOG.debug("Found default route, gateway is %s", gw)
return gw
return None
def get_dhclient_d():
# find lease files directory
supported_dirs = [
"/var/lib/dhclient",
"/var/lib/dhcp",
"/var/lib/NetworkManager",
]
for d in supported_dirs:
if os.path.exists(d) and len(os.listdir(d)) > 0:
LOG.debug("Using %s lease directory", d)
return d
return None
def get_latest_lease(lease_d=None):
# find latest lease file
if lease_d is None:
lease_d = get_dhclient_d()
if not lease_d:
return None
lease_files = os.listdir(lease_d)
latest_mtime = -1
latest_file = None
# lease files are named inconsistently across distros.
# We assume that 'dhclient6' indicates ipv6 and ignore it.
# ubuntu:
# dhclient.<iface>.leases, dhclient.leases, dhclient6.leases
# centos6:
# dhclient-<iface>.leases, dhclient6.leases
# centos7: ('--' is not a typo)
# dhclient--<iface>.lease, dhclient6.leases
for fname in lease_files:
if fname.startswith("dhclient6"):
# avoid files that start with dhclient6 assuming dhcpv6.
continue
if not (fname.endswith(".lease") or fname.endswith(".leases")):
continue
abs_path = os.path.join(lease_d, fname)
mtime = os.path.getmtime(abs_path)
if mtime > latest_mtime:
latest_mtime = mtime
latest_file = abs_path
return latest_file
def get_vr_address():
# Get the address of the virtual router via dhcp leases
# If no virtual router is detected, fallback on default gateway.
# See http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/4.8/virtual_machines/user-data.html # noqa
# Try data-server DNS entry first
latest_address = get_data_server()
if latest_address:
LOG.debug(
"Found metadata server '%s' via data-server DNS entry",
latest_address,
)
return latest_address
# Try networkd second...
latest_address = dhcp.networkd_get_option_from_leases("SERVER_ADDRESS")
if latest_address:
LOG.debug(
"Found SERVER_ADDRESS '%s' via networkd_leases", latest_address
)
return latest_address
# Try dhcp lease files next...
lease_file = get_latest_lease()
if not lease_file:
LOG.debug("No lease file found, using default gateway")
return get_default_gateway()
with open(lease_file, "r") as fd:
for line in fd:
if "dhcp-server-identifier" in line:
words = line.strip(" ;\r\n").split(" ")
if len(words) > 2:
dhcptok = words[2]
LOG.debug("Found DHCP identifier %s", dhcptok)
latest_address = dhcptok
if not latest_address:
# No virtual router found, fallback on default gateway
LOG.debug("No DHCP found, using default gateway")
return get_default_gateway()
return latest_address
# Used to match classes to dependencies
datasources = [
(DataSourceCloudStack, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
]
# Return a list of data sources that match this set of dependencies
def get_datasource_list(depends):
return sources.list_from_depends(depends, datasources)
# vi: ts=4 expandtab
|