85
90
'RamDiskId': {'Type': 'String',
86
91
'Implemented': False},
87
92
'SecurityGroups': {'Type': 'List'},
88
'SecurityGroupIds': {'Type': 'List',
89
'Implemented': False},
93
'SecurityGroupIds': {'Type': 'List'},
90
94
'NetworkInterfaces': {'Type': 'List'},
91
95
'SourceDestCheck': {'Type': 'Boolean',
92
96
'Implemented': False},
93
'SubnetId': {'Type': 'String',
94
'Implemented': False},
97
'SubnetId': {'Type': 'String'},
95
98
'Tags': {'Type': 'List',
96
99
'Schema': {'Type': 'Map',
97
100
'Schema': tags_schema}},
106
109
'UserData': {'Type': 'String'},
107
110
'Volumes': {'Type': 'List'}}
112
attributes_schema = {'AvailabilityZone': ('The Availability Zone where the'
113
' specified instance is '
115
'PrivateDnsName': ('Private DNS name of the specified'
117
'PublicDnsName': ('Public DNS name of the specified '
119
'PrivateIp': ('Private IP address of the specified '
121
'PublicIp': ('Public IP address of the specified '
109
124
# template keys supported for handle_update, note trailing comma
110
125
# is required for a single item to get a tuple not a string
111
126
update_allowed_keys = ('Metadata',)
128
_deferred_server_statuses = ['BUILD',
113
139
def __init__(self, name, json_snippet, stack):
114
140
super(Instance, self).__init__(name, json_snippet, stack)
115
141
self.ipaddress = None
116
142
self.mime_string = None
117
self._server_status = None
119
144
def _set_ipaddress(self, networks):
140
166
return self.ipaddress or '0.0.0.0'
142
def FnGetAtt(self, key):
168
def _resolve_attribute(self, name):
144
if key == 'AvailabilityZone':
170
if name == 'AvailabilityZone':
145
171
res = self.properties['AvailabilityZone']
146
elif key == 'PublicIp':
147
res = self._ipaddress()
148
elif key == 'PrivateIp':
149
res = self._ipaddress()
150
elif key == 'PublicDnsName':
151
res = self._ipaddress()
152
elif key == 'PrivateDnsName':
153
res = self._ipaddress()
155
raise exception.InvalidTemplateAttribute(resource=self.name,
172
elif name in ['PublicIp', 'PrivateIp', 'PublicDnsName',
174
res = self._ipaddress()
158
logger.info('%s.GetAtt(%s) == %s' % (self.name, key, res))
176
logger.info('%s._resolve_attribute(%s) == %s' % (self.name, name, res))
177
return unicode(res) if res else None
161
179
def _build_userdata(self, userdata):
162
180
if not self.mime_string:
222
240
return self.mime_string
225
def _build_nics(network_interfaces):
226
if not network_interfaces:
230
for nic in network_interfaces:
231
if isinstance(nic, basestring):
233
'NetworkInterfaceId': nic,
234
'DeviceIndex': len(nics)})
237
sorted_nics = sorted(nics, key=lambda nic: int(nic['DeviceIndex']))
239
return [{'port-id': nic['NetworkInterfaceId']} for nic in sorted_nics]
242
def _build_nics(self, network_interfaces, subnet_id=None):
246
if network_interfaces:
248
for entry in network_interfaces:
250
if not isinstance(entry, basestring)
251
else {'NetworkInterfaceId': entry,
252
'DeviceIndex': len(unsorted_nics)})
253
unsorted_nics.append(nic)
254
sorted_nics = sorted(unsorted_nics,
255
key=lambda nic: int(nic['DeviceIndex']))
256
nics = [{'port-id': nic['NetworkInterfaceId']}
257
for nic in sorted_nics]
259
# if SubnetId property in Instance, ensure subnet exists
261
quantumclient = self.quantum()
262
network_id = NetworkInterface.network_id_from_subnet_id(
263
quantumclient, subnet_id)
264
# if subnet verified, create a port to use this subnet
265
# if port is not created explicitly, nova will choose
266
# the first subnet in the given network.
268
fixed_ip = {'subnet_id': subnet_id}
270
'admin_state_up': True,
271
'network_id': network_id,
272
'fixed_ips': [fixed_ip]
274
port = quantumclient.create_port({'port': props})['port']
275
nics = [{'port-id': port['id']}]
279
def _get_security_groups(self):
281
for property in ('SecurityGroups', 'SecurityGroupIds'):
282
if self.properties.get(property) is not None:
283
for sg in self.properties.get(property):
284
security_groups.append(sg)
285
if not security_groups:
286
security_groups = None
287
return security_groups
241
289
def handle_create(self):
242
if self.properties.get('SecurityGroups') is None:
243
security_groups = None
245
security_groups = [self.physical_resource_name_find(sg)
246
for sg in self.properties.get('SecurityGroups')]
290
security_groups = self._get_security_groups()
248
292
userdata = self.properties['UserData'] or ''
249
293
flavor = self.properties['InstanceType']
311
348
if server is not None:
312
349
self.resource_id_set(server.id)
314
self._server_status = server.status
316
def check_active(self):
317
if self._server_status == 'ACTIVE':
320
server = self.nova().servers.get(self.resource_id)
321
self._server_status = server.status
322
if server.status == 'BUILD':
324
if server.status == 'ACTIVE':
325
self._set_ipaddress(server.networks)
326
self.attach_volumes()
351
return server, scheduler.TaskRunner(self._attach_volumes_task())
353
def _attach_volumes_task(self):
354
attach_tasks = (volume.VolumeAttachTask(self.stack,
358
for volume_id, device in self.volumes())
359
return scheduler.PollingTaskGroup(attach_tasks)
361
def check_create_complete(self, cookie):
362
return self._check_active(cookie)
364
def _check_active(self, cookie):
365
server, volume_attach = cookie
367
if not volume_attach.started():
368
if server.status != 'ACTIVE':
371
# Some clouds append extra (STATUS) strings to the status
372
short_server_status = server.status.split('(')[0]
373
if short_server_status in self._deferred_server_statuses:
375
elif server.status == 'ACTIVE':
376
self._set_ipaddress(server.networks)
377
volume_attach.start()
378
return volume_attach.done()
380
raise exception.Error('%s instance[%s] status[%s]' %
381
('nova reported unexpected',
382
self.name, server.status))
329
raise exception.Error('%s instance[%s] status[%s]' %
330
('nova reported unexpected',
331
self.name, server.status))
333
def attach_volumes(self):
334
if not self.properties['Volumes']:
336
server_id = self.resource_id
337
for vol in self.properties['Volumes']:
338
if 'DeviceId' in vol:
339
dev = vol['DeviceId']
342
self.stack.clients.attach_volume_to_instance(server_id,
346
def detach_volumes(self):
347
server_id = self.resource_id
348
for vol in self.properties['Volumes']:
349
self.stack.clients.detach_volume_from_instance(server_id,
352
def handle_update(self, json_snippet):
353
status = self.UPDATE_REPLACE
355
tmpl_diff = self.update_template_diff(json_snippet)
356
except NotImplementedError:
357
return self.UPDATE_REPLACE
361
self.metadata = json_snippet.get('Metadata', {})
362
status = self.UPDATE_COMPLETE
364
return self.UPDATE_REPLACE
384
return volume_attach.step()
388
Return an iterator over (volume_id, device) tuples for all volumes
389
that should be attached to this instance.
391
volumes = self.properties['Volumes']
395
return ((vol['VolumeId'], vol['Device']) for vol in volumes)
397
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
398
if 'Metadata' in tmpl_diff:
399
self.metadata = tmpl_diff.get('Metadata', {})
368
401
def metadata_update(self, new_metadata=None):
383
416
# check validity of key
385
key_name = self.properties['KeyName']
417
key_name = self.properties.get('KeyName', None)
391
419
keypairs = self.nova().keypairs.list()
393
if k.name == key_name:
396
'Provided KeyName is not registered with nova'}
420
if not any(k.name == key_name for k in keypairs):
422
'Provided KeyName is not registered with nova'}
424
# check validity of security groups vs. network interfaces
425
security_groups = self._get_security_groups()
426
if security_groups and self.properties.get('NetworkInterfaces'):
428
'Cannot define both SecurityGroups/SecurityGroupIds and '
429
'NetworkInterfaces properties.'}
431
# make sure the image exists.
432
image_identifier = self.properties['ImageId']
434
self._get_image_id(image_identifier)
435
except exception.ImageNotFound:
436
return {'Error': 'Image %s was not found in glance' %
438
except exception.NoUniqueImageFound:
439
return {'Error': 'Multiple images were found with name %s' %
444
def _delete_server(self, server):
446
Return a co-routine that deletes the server and waits for it to
456
except clients.novaclient.exceptions.NotFound:
459
def _detach_volumes_task(self):
461
Detach volumes from the instance
463
detach_tasks = (volume.VolumeDetachTask(self.stack,
466
for volume_id, device in self.volumes())
467
return scheduler.PollingTaskGroup(detach_tasks)
398
469
def handle_delete(self):
402
473
if self.resource_id is None:
405
if self.properties['Volumes']:
406
self.detach_volumes()
476
scheduler.TaskRunner(self._detach_volumes_task())()
409
479
server = self.nova().servers.get(self.resource_id)
410
480
except clients.novaclient.exceptions.NotFound:
417
except clients.novaclient.exceptions.NotFound:
483
delete = scheduler.TaskRunner(self._delete_server, server)
484
delete(wait_time=0.2)
420
486
self.resource_id = None
488
def _get_image_id(self, image_identifier):
490
if uuidutils.is_uuid_like(image_identifier):
492
image_id = self.nova().images.get(image_identifier).id
493
except clients.novaclient.exceptions.NotFound:
494
logger.info("Image %s was not found in glance"
496
raise exception.ImageNotFound(image_name=image_identifier)
499
image_list = self.nova().images.list()
500
except clients.novaclient.exceptions.ClientException as ex:
501
raise exception.ServerError(message=str(ex))
504
for o in image_list if o.name == image_identifier)
505
if len(image_names) == 0:
506
logger.info("Image %s was not found in glance" %
508
raise exception.ImageNotFound(image_name=image_identifier)
509
elif len(image_names) > 1:
510
logger.info("Mulitple images %s were found in glance with name"
512
raise exception.NoUniqueImageFound(image_name=image_identifier)
513
image_id = image_names.popitem()[0]
516
def handle_suspend(self):
518
Suspend an instance - note we do not wait for the SUSPENDED state,
519
this is polled for by check_suspend_complete in a similar way to the
520
create logic so we can take advantage of coroutines
522
if self.resource_id is None:
523
raise exception.Error(_('Cannot suspend %s, resource_id not set') %
527
server = self.nova().servers.get(self.resource_id)
528
except clients.novaclient.exceptions.NotFound:
529
raise exception.NotFound(_('Failed to find instance %s') %
532
logger.debug("suspending instance %s" % self.resource_id)
533
# We want the server.suspend to happen after the volume
534
# detachement has finished, so pass both tasks and the server
535
suspend_runner = scheduler.TaskRunner(server.suspend)
536
volumes_runner = scheduler.TaskRunner(self._detach_volumes_task())
537
return server, suspend_runner, volumes_runner
539
def check_suspend_complete(self, cookie):
540
server, suspend_runner, volumes_runner = cookie
542
if not volumes_runner.started():
543
volumes_runner.start()
545
if volumes_runner.done():
546
if not suspend_runner.started():
547
suspend_runner.start()
549
if suspend_runner.done():
550
if server.status == 'SUSPENDED':
554
logger.debug("%s check_suspend_complete status = %s" %
555
(self.name, server.status))
556
if server.status in list(self._deferred_server_statuses +
558
return server.status == 'SUSPENDED'
560
raise exception.Error(_(' nova reported unexpected '
561
'instance[%(instance)s] '
562
'status[%(status)s]') %
563
{'instance': self.name,
564
'status': server.status})
566
suspend_runner.step()
568
return volumes_runner.step()
570
def handle_resume(self):
572
Resume an instance - note we do not wait for the ACTIVE state,
573
this is polled for by check_resume_complete in a similar way to the
574
create logic so we can take advantage of coroutines
576
if self.resource_id is None:
577
raise exception.Error(_('Cannot resume %s, resource_id not set') %
581
server = self.nova().servers.get(self.resource_id)
582
except clients.novaclient.exceptions.NotFound:
583
raise exception.NotFound(_('Failed to find instance %s') %
586
logger.debug("resuming instance %s" % self.resource_id)
588
return server, scheduler.TaskRunner(self._attach_volumes_task())
590
def check_resume_complete(self, cookie):
591
return self._check_active(cookie)
423
594
def resource_mapping():