37
44
from warnings import warn
39
from apiclient.maas_client import (
45
from provisioningserver.auth import get_recorded_api_credentials
46
from provisioningserver.cluster_config import get_maas_url
47
from provisioningserver.cluster_config import get_cluster_uuid
48
from provisioningserver.logger.log import get_maas_logger
49
from provisioningserver.rpc import getRegionClient
50
from provisioningserver.rpc.exceptions import (
51
NoConnectionsAvailable,
54
from provisioningserver.utils.twisted import (
47
58
import simplejson as json
60
from twisted.internet import reactor
61
from twisted.internet.defer import (
65
from twisted.protocols.amp import UnhandledCommand
68
maaslog = get_maas_logger("utils")
51
71
def node_exists(macs, url, client):
66
86
return len(content) > 0
69
90
def create_node(macs, arch, power_type, power_parameters):
70
api_credentials = get_recorded_api_credentials()
71
if api_credentials is None:
72
raise Exception('Not creating node: no API key yet.')
74
MAASOAuth(*api_credentials), MAASDispatcher(),
91
"""Create a Node on the region and return its system_id.
79
'power_type': power_type,
80
'power_parameters': json.dumps(power_parameters),
81
'mac_addresses': macs,
82
'autodetect_nodegroup': 'true'
84
url = '/api/1.0/nodes/'
85
if node_exists(macs, url, client):
93
:param macs: A list of MAC addresses belonging to the node.
94
:param arch: The node's architecture, in the form 'arch/subarch'.
95
:param power_type: The node's power type as a string.
96
:param power_parameters: The power parameters for the node, as a
99
# Avoid circular dependencies.
100
from provisioningserver.rpc.region import CreateNode
101
for elapsed, remaining, wait in retries(15, 5, reactor):
103
client = getRegionClient()
105
except NoConnectionsAvailable:
106
yield pause(wait, reactor)
109
"Can't create node, no RPC connection to region.")
87
return client.post(url, 'new', **data)
112
# De-dupe the MAC addresses we pass. We sort here to avoid test
114
macs = sorted(set(macs))
116
response = yield client(
118
cluster_uuid=get_cluster_uuid(),
120
power_type=power_type,
121
power_parameters=json.dumps(power_parameters),
123
except NodeAlreadyExists:
124
# The node already exists on the region, so we log the error and
127
"A node with one of the mac addressess in %s already exists.",
130
except UnhandledCommand:
131
# The region hasn't been upgraded to support this method
134
"Unable to create node on region: Region does not "
135
"support the CreateNode RPC method.")
138
returnValue(response['system_id'])
90
141
def locate_config(*path):
110
161
return os.path.abspath(os.path.join(config_dir, *path))
164
setting_expression = r"""
165
^([A-Z0-9_]+) # Variable name is all caps, alphanumeric and _.
166
= # Assignment operator.
167
(?:"|\')? # Optional leading single or double quote.
169
(?:"|\')? # Optional trailing single or double quote.
173
def get_cluster_config(config_path):
174
contents = open(config_path).read()
176
results = re.findall(
177
setting_expression, contents, re.MULTILINE | re.VERBOSE)
113
182
def find_settings(whence):
114
183
"""Return settings from `whence`, which is assumed to be a module."""
115
184
# XXX 2012-10-11 JeroenVermeulen, bug=1065456: Put this in a shared
215
def dict_depth(d, depth=0):
216
"""Returns the max depth of a dictionary."""
217
if not isinstance(d, dict) or not d:
219
return max(dict_depth(v, depth + 1) for _, v in d.iteritems())
146
222
def split_lines(input, separator):
147
223
"""Split each item from `input` into a key/value pair."""
148
224
return (line.split(separator, 1) for line in input if line.strip() != '')
346
422
message = "%s; %s" % (message, alternative)
347
423
warn(message, DeprecationWarning, 1)
426
def flatten(*things):
427
"""Recursively flatten iterable parts of `things`.
431
>>> sorted(flatten([1, 2, {3, 4, (5, 6)}]))
434
:returns: An iterator.
436
def _flatten(things):
437
if isinstance(things, basestring):
438
# String-like objects are treated as leaves; iterating through a
439
# string yields more strings, each of which is also iterable, and
440
# so on, until the heat-death of the universe.
441
return iter((things,))
442
elif isinstance(things, Iterable):
443
# Recurse and merge in order to flatten nested structures.
444
return chain.from_iterable(imap(_flatten, things))
446
# This is a leaf; return an single-item iterator so that it can be
447
# chained with any others.
448
return iter((things,))
450
return _flatten(things)