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 | root@vmaas-21:~# apt-cache policy maas
maas:
Installed: 2.1.0+bzr5480-0ubuntu1~16.04.1
Candidate: 2.1.0+bzr5480-0ubuntu1~16.04.1
Version table:
*** 2.1.0+bzr5480-0ubuntu1~16.04.1 500
500 http://ppa.launchpad.net/maas/next/ubuntu xenial/main amd64 Packages
100 /var/lib/dpkg/status
2.0.0+bzr5189-0ubuntu1~16.04.1 500
500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages
2.0.0~beta3+bzr4941-0ubuntu1 500
500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
-----------------
root@vmaas-21:~# grep not_networks -A100 -B100 /usr/lib/python3/dist-packages/maasserver/api/machines.py
request.user, perm=NODE_PERMISSION.EDIT, ids=system_ids)
if len(machines) < len(system_ids):
permitted_ids = set(machine.system_id for machine in machines)
raise PermissionDenied(
"You don't have the required permission to release the "
"following machine(s): %s." % (
', '.join(system_ids - permitted_ids)))
released_ids = []
failed = []
for machine in machines:
if machine.status == NODE_STATUS.READY:
# Nothing to do.
pass
elif machine.status in RELEASABLE_STATUSES:
machine.release_or_erase(request.user, comment)
released_ids.append(machine.system_id)
else:
failed.append(
"%s ('%s')"
% (machine.system_id, machine.display_status()))
if any(failed):
raise NodeStateViolation(
"Machine(s) cannot be released in their current state: %s."
% ', '.join(failed))
return released_ids
@operation(idempotent=True)
def list_allocated(self, request):
"""Fetch Machines that were allocated to the User/oauth token."""
token = get_oauth_token(request)
match_ids = get_optional_list(request.GET, 'id')
machines = Machine.objects.get_allocated_visible_machines(
token, match_ids)
return machines.order_by('id')
@operation(idempotent=False)
def allocate(self, request):
"""Allocate an available machine for deployment.
Constraints parameters can be used to allocate a machine that possesses
certain characteristics. All the constraints are optional and when
multiple constraints are provided, they are combined using 'AND'
semantics.
:param name: Hostname of the desired machine.
:type name: unicode
:param system_id: system_id of the desired machine.
:type system_id: unicode
:param arch: Architecture of the returned machine (e.g. 'i386/generic',
'amd64', 'armhf/highbank', etc.).
:type arch: unicode
:param cpu_count: The minium number of CPUs the returned machine must
have.
:type cpu_count: int
:param interfaces: A labeled constraint map associating constraint
labels with interface properties that should be matched. Returned
nodes must have one or more interface matching the specified
constraints. The labeled constraint map must be in the format:
``<label>:<key>=<value>[,<key2>=<value2>[,...]]``
Each key can be one of the following:
- id: Matches an interface with the specific id
- fabric: Matches an interface attached to the specified fabric.
- fabric_class: Matches an interface attached to a fabric
with the specified class.
- ip: Matches an interface with the specified IP address
assigned to it.
- mode: Matches an interface with the specified mode. (Currently,
the only supported mode is "unconfigured".)
- name: Matches an interface with the specified name.
(For example, "eth0".)
- hostname: Matches an interface attached to the node with
the specified hostname.
- subnet: Matches an interface attached to the specified subnet.
- space: Matches an interface attached to the specified space.
- subnet_cidr: Matches an interface attached to the specified
subnet CIDR. (For example, "192.168.0.0/24".)
- type: Matches an interface of the specified type. (Valid
types: "physical", "vlan", "bond", "bridge", or "unknown".)
- vlan: Matches an interface on the specified VLAN.
- vid: Matches an interface on a VLAN with the specified VID.
- tag: Matches an interface tagged with the specified tag.
:type interfaces: unicode
:param mem: The minimum amount of memory (expressed in MB) the
returned machine must have.
:type mem: float
:param tags: List of tags the returned machine must have.
:type tags: list of unicodes
:param not_tags: List of tags the acquired machine must not have.
:type tags: List of unicodes.
:param networks: List of networks (defined in MAAS) to which the
machine must be attached. A network can be identified by the name
assigned to it in MAAS; or by an `ip:` prefix followed by any IP
address that falls within the network; or a `vlan:` prefix
followed by a numeric VLAN tag, e.g. `vlan:23` for VLAN number 23.
Valid VLAN tags must be in the range of 1 to 4094 inclusive.
:type networks: list of unicodes
:param not_networks: List of networks (defined in MAAS) to which the
machine must not be attached. The returned machine won't be
attached to any of the specified networks. A network can be
identified by the name assigned to it in MAAS; or by an `ip:`
prefix followed by any IP address that falls within the network; or
a `vlan:` prefix followed by a numeric VLAN tag, e.g. `vlan:23` for
VLAN number 23. Valid VLAN tags must be in the range of 1 to 4094
inclusive.
:type not_networks: list of unicodes
:param zone: An optional name for a physical zone the acquired
machine should be located in.
:type zone: unicode
:type not_in_zone: Optional list of physical zones from which the
machine should not be acquired.
:type not_in_zone: List of unicodes.
:param agent_name: An optional agent name to attach to the
acquired machine.
:type agent_name: unicode
:param comment: Optional comment for the event log.
:type comment: unicode
:param bridge_all: Optionally create a bridge interface for every
configured interface on the machine. The created bridges will be
removed once the machine is released.
(Default: False)
:type bridge_all: boolean
:param bridge_stp: Optionally turn spanning tree protocol on or off
for the bridges created on every configured interface.
(Default: off)
:type bridge_stp: boolean
:param bridge_fd: Optionally adjust the forward delay to time seconds.
(Default: 15)
:type bridge_fd: integer
:param dry_run: Optional boolean to indicate that the machine should
not actually be acquired (this is for support/troubleshooting, or
users who want to see which machine would match a constraint,
without acquiring a machine). Defaults to False.
:type dry_run: bool
:param verbose: Optional boolean to indicate that the user would like
additional verbosity in the constraints_by_type field (each
constraint will be prefixed by `verbose_`, and contain the full
data structure that indicates which machine(s) matched).
:type verbose: bool
Returns 409 if a suitable machine matching the constraints could not be
found.
"""
form = AcquireNodeForm(data=request.data)
comment = get_optional_param(request.POST, 'comment')
maaslog.info(
"Request from user %s to acquire a machine with constraints %s",
request.user.username, request.data)
bridge_all = get_optional_param(
request.POST, 'bridge_all', default=False, validator=StringBool)
bridge_stp = get_optional_param(
request.POST, 'bridge_stp', default=False, validator=StringBool)
bridge_fd = get_optional_param(
request.POST, 'bridge_fd', default=False, validator=Int)
verbose = get_optional_param(
request.POST, 'verbose', default=False, validator=StringBool)
dry_run = get_optional_param(
request.POST, 'dry_run', default=False, validator=StringBool)
if not form.is_valid():
raise MAASAPIValidationError(form.errors)
# This lock prevents a machine we've picked as available from
# becoming unavailable before our transaction commits.
with locks.node_acquire:
machines = (
self.base_model.objects.get_available_machines_for_acquisition(
request.user)
)
machines, storage, interfaces = form.filter_nodes(machines)
machine = get_first(machines)
if machine is None:
constraints = form.describe_constraints()
if constraints == '':
# No constraints. That means no machines at all were
# available.
message = "No machine available."
else:
message = (
"No available machine matches constraints: %s"
% constraints)
raise NodesNotAvailable(message)
agent_name = request.data.get('agent_name', '')
if not dry_run:
machine.acquire(
request.user, get_oauth_token(request),
agent_name=agent_name, comment=comment,
bridge_all=bridge_all, bridge_stp=bridge_stp,
bridge_fd=bridge_fd)
machine.constraint_map = storage.get(machine.id, {})
machine.constraints_by_type = {}
# Need to get the interface constraints map into the proper format
# to return it here.
# Backward compatibility: provide the storage constraints in both
# formats.
if len(machine.constraint_map) > 0:
machine.constraints_by_type['storage'] = {}
new_storage = machine.constraints_by_type['storage']
# Convert this to the "new style" constraints map format.
for storage_key in machine.constraint_map:
# Each key in the storage map is actually a value which
# contains the ID of the matching storage device.
# Convert this to a label: list-of-matches format, to
# match how the constraints will be done going forward.
new_key = machine.constraint_map[storage_key]
matches = new_storage.get(new_key, [])
|