2 # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # * See the License for the specific language governing permissions and
14 # * limitations under the License.
22 from novaclient import exceptions as nova_exceptions
24 from cloudify import ctx
25 from cloudify.manager import get_rest_client
26 from cloudify.decorators import operation
27 from cloudify.exceptions import NonRecoverableError, RecoverableError
28 from cinder_plugin import volume
29 from openstack_plugin_common import (
31 transform_resource_name,
33 get_openstack_ids_of_connected_nodes_by_openstack_type,
36 assign_payload_as_runtime_properties,
37 get_openstack_id_of_single_connected_node_by_openstack_type,
38 get_openstack_names_of_connected_nodes_by_openstack_type,
39 get_single_connected_node_by_openstack_type,
41 is_external_resource_by_properties,
42 is_external_resource_not_conditionally_created,
43 is_external_relationship_not_conditionally_created,
44 use_external_resource,
45 delete_runtime_properties,
46 is_external_relationship,
48 USE_EXTERNAL_RESOURCE_PROPERTY,
49 OPENSTACK_AZ_PROPERTY,
50 OPENSTACK_ID_PROPERTY,
51 OPENSTACK_TYPE_PROPERTY,
52 OPENSTACK_NAME_PROPERTY,
53 COMMON_RUNTIME_PROPERTIES_KEYS,
55 from nova_plugin.keypair import KEYPAIR_OPENSTACK_TYPE
56 from nova_plugin import userdata
57 from openstack_plugin_common.floatingip import (IP_ADDRESS_PROPERTY,
58 get_server_floating_ip)
59 from neutron_plugin.network import NETWORK_OPENSTACK_TYPE
60 from neutron_plugin.port import PORT_OPENSTACK_TYPE
61 from cinder_plugin.volume import VOLUME_OPENSTACK_TYPE
62 from openstack_plugin_common.security_group import \
63 SECURITY_GROUP_OPENSTACK_TYPE
64 from glance_plugin.image import handle_image_from_relationship
66 SERVER_OPENSTACK_TYPE = 'server'
68 # server status constants. Full lists here: http://docs.openstack.org/api/openstack-compute/2/content/List_Servers-d1e2078.html # NOQA
69 SERVER_STATUS_ACTIVE = 'ACTIVE'
70 SERVER_STATUS_BUILD = 'BUILD'
71 SERVER_STATUS_SHUTOFF = 'SHUTOFF'
73 OS_EXT_STS_TASK_STATE = 'OS-EXT-STS:task_state'
74 SERVER_TASK_STATE_POWERING_ON = 'powering-on'
76 MUST_SPECIFY_NETWORK_EXCEPTION_TEXT = 'More than one possible network found.'
77 SERVER_DELETE_CHECK_SLEEP = 2
80 NETWORKS_PROPERTY = 'networks' # all of the server's ips
81 IP_PROPERTY = 'ip' # the server's private ip
82 ADMIN_PASSWORD_PROPERTY = 'password' # the server's password
83 RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS + \
84 [NETWORKS_PROPERTY, IP_PROPERTY, ADMIN_PASSWORD_PROPERTY]
87 def _get_management_network_id_and_name(neutron_client, ctx):
88 """Examine the context to find the management network id and name."""
89 management_network_id = None
90 management_network_name = None
91 provider_context = provider(ctx)
93 if ('management_network_name' in ctx.node.properties) and \
94 ctx.node.properties['management_network_name']:
95 management_network_name = \
96 ctx.node.properties['management_network_name']
97 management_network_name = transform_resource_name(
98 ctx, management_network_name)
99 management_network_id = neutron_client.cosmo_get_named(
100 'network', management_network_name)
101 management_network_id = management_network_id['id']
103 int_network = provider_context.int_network
105 management_network_id = int_network['id']
106 management_network_name = int_network['name'] # Already transform.
108 return management_network_id, management_network_name
111 def _merge_nics(management_network_id, *nics_sources):
112 """Merge nics_sources into a single nics list, insert mgmt network if
114 nics_sources are lists of networks received from several sources
115 (server properties, relationships to networks, relationships to ports).
116 Merge them into a single list, and if the management network isn't present
117 there, prepend it as the first network.
120 for nics in nics_sources:
122 if management_network_id is not None and \
123 not any(nic['net-id'] == management_network_id for nic in merged):
124 merged.insert(0, {'net-id': management_network_id})
128 def _normalize_nics(nics):
129 """Transform the NICs passed to the form expected by openstack.
131 If both net-id and port-id are provided, remove net-id: it is ignored
135 if 'port-id' in nic and 'net-id' in nic:
139 return [_normalize(nic) for nic in nics]
142 def _prepare_server_nics(neutron_client, ctx, server):
143 """Update server['nics'] based on declared relationships.
145 server['nics'] should contain the pre-declared nics, then the networks
146 that the server has a declared relationship to, then the networks
147 of the ports the server has a relationship to.
149 If that doesn't include the management network, it should be prepended
150 as the first network.
152 The management network id and name are stored in the server meta properties
154 network_ids = get_openstack_ids_of_connected_nodes_by_openstack_type(
155 ctx, NETWORK_OPENSTACK_TYPE)
156 port_ids = get_openstack_ids_of_connected_nodes_by_openstack_type(
157 ctx, PORT_OPENSTACK_TYPE)
158 management_network_id, management_network_name = \
159 _get_management_network_id_and_name(neutron_client, ctx)
160 if management_network_id is None and (network_ids or port_ids):
162 raise NonRecoverableError(
163 "Nova server with NICs requires "
164 "'management_network_name' in properties or id "
165 "from provider context, which was not supplied")
168 management_network_id,
169 server.get('nics', []),
170 [{'net-id': net_id} for net_id in network_ids],
171 get_port_networks(neutron_client, port_ids))
173 nics = _normalize_nics(nics)
175 server['nics'] = nics
176 if management_network_id is not None:
177 server['meta']['cloudify_management_network_id'] = \
178 management_network_id
179 if management_network_name is not None:
180 server['meta']['cloudify_management_network_name'] = \
181 management_network_name
184 def _get_boot_volume_relationships(type_name, ctx):
185 ctx.logger.debug('Instance relationship target instances: {0}'.format(str([
186 rel.target.instance.runtime_properties
187 for rel in ctx.instance.relationships])))
190 for rel in ctx.instance.relationships
191 if rel.target.instance.runtime_properties.get(
192 OPENSTACK_TYPE_PROPERTY) == type_name and
193 rel.target.node.properties.get('boot', False)]
197 elif len(targets) > 1:
198 raise NonRecoverableError("2 boot volumes not supported")
202 def _handle_boot_volume(server, ctx):
203 boot_volume = _get_boot_volume_relationships(VOLUME_OPENSTACK_TYPE, ctx)
205 boot_volume_id = boot_volume.runtime_properties[OPENSTACK_ID_PROPERTY]
206 ctx.logger.info('boot_volume_id: {0}'.format(boot_volume_id))
207 az = boot_volume.runtime_properties[OPENSTACK_AZ_PROPERTY]
208 # If a block device mapping already exists we shouldn't overwrite it
210 bdm = server.setdefault('block_device_mapping', {})
211 bdm['vda'] = '{0}:::0'.format(boot_volume_id)
212 # Some nova configurations allow cross-az server-volume connections, so
213 # we can't treat that as an error.
214 if not server.get('availability_zone'):
215 server['availability_zone'] = az
221 def create(nova_client, neutron_client, args, **kwargs):
223 Creates a server. Exposes the parameters mentioned in
224 http://docs.openstack.org/developer/python-novaclient/api/novaclient.v1_1
225 .servers.html#novaclient.v1_1.servers.ServerManager.create
228 external_server = use_external_resource(ctx, nova_client,
229 SERVER_OPENSTACK_TYPE)
232 _set_network_and_ip_runtime_properties(external_server)
237 get_openstack_ids_of_connected_nodes_by_openstack_type(
238 ctx, NETWORK_OPENSTACK_TYPE)
239 port_ids = get_openstack_ids_of_connected_nodes_by_openstack_type(
240 ctx, PORT_OPENSTACK_TYPE)
242 _validate_external_server_nics(
247 _validate_external_server_keypair(nova_client)
250 delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS)
253 provider_context = provider(ctx)
256 return transform_resource_name(ctx, name)
259 'name': get_resource_id(ctx, SERVER_OPENSTACK_TYPE),
261 server.update(copy.deepcopy(ctx.node.properties['server']))
262 server.update(copy.deepcopy(args))
264 _handle_boot_volume(server, ctx)
265 handle_image_from_relationship(server, 'image', ctx)
267 if 'meta' not in server:
268 server['meta'] = dict()
270 transform_resource_name(ctx, server)
273 "server.create() server before transformations: {0}".format(server))
275 for key in 'block_device_mapping', 'block_device_mapping_v2':
277 # if there is a connected boot volume, don't require the `image`
279 # However, python-novaclient requires an `image` input anyway, and
280 # checks it for truthiness when deciding whether to pass it along
282 if 'image' not in server:
283 server['image'] = ctx.node.properties.get('image')
286 _handle_image_or_flavor(server, nova_client, 'image')
287 _handle_image_or_flavor(server, nova_client, 'flavor')
289 if provider_context.agents_security_group:
290 security_groups = server.get('security_groups', [])
291 asg = provider_context.agents_security_group['name']
292 if asg not in security_groups:
293 security_groups.append(asg)
294 server['security_groups'] = security_groups
295 elif not server.get('security_groups', []):
296 # Make sure that if the server is connected to a security group
297 # from CREATE time so that there the user can control
298 # that there is never a time that a running server is not protected.
299 security_group_names = \
300 get_openstack_names_of_connected_nodes_by_openstack_type(
302 SECURITY_GROUP_OPENSTACK_TYPE)
303 server['security_groups'] = security_group_names
305 # server keypair handling
306 keypair_id = get_openstack_id_of_single_connected_node_by_openstack_type(
307 ctx, KEYPAIR_OPENSTACK_TYPE, True)
309 if 'key_name' in server:
311 raise NonRecoverableError("server can't both have the "
312 '"key_name" nested property and be '
313 'connected to a keypair via a '
314 'relationship at the same time')
315 server['key_name'] = rename(server['key_name'])
317 server['key_name'] = _get_keypair_name_by_id(nova_client, keypair_id)
318 elif provider_context.agents_keypair:
319 server['key_name'] = provider_context.agents_keypair['name']
321 server['key_name'] = None
323 'server must have a keypair, yet no keypair was connected to the '
324 'server node, the "key_name" nested property '
325 "wasn't used, and there is no agent keypair in the provider "
326 "context. Agent installation can have issues.")
328 _fail_on_missing_required_parameters(
333 _prepare_server_nics(neutron_client, ctx, server)
336 "server.create() server after transformations: {0}".format(server))
338 userdata.handle_userdata(server)
340 ctx.logger.info("Creating VM with parameters: {0}".format(str(server)))
341 # Store the server dictionary contents in runtime properties
342 assign_payload_as_runtime_properties(ctx, SERVER_OPENSTACK_TYPE, server)
344 "Asking Nova to create server. All possible parameters are: {0})"
345 .format(','.join(server.keys())))
348 s = nova_client.servers.create(**server)
349 except nova_exceptions.BadRequest as e:
350 if 'Block Device Mapping is Invalid' in str(e):
351 return ctx.operation.retry(
352 message='Block Device Mapping is not created yet',
354 if str(e).startswith(MUST_SPECIFY_NETWORK_EXCEPTION_TEXT):
355 raise NonRecoverableError(
356 "Can not provision server: management_network_name or id"
357 " is not specified but there are several networks that the "
358 "server can be connected to.")
360 ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = s.id
361 ctx.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] = \
362 SERVER_OPENSTACK_TYPE
363 ctx.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = server['name']
366 def get_port_networks(neutron_client, port_ids):
368 def get_network(port_id):
369 port = neutron_client.show_port(port_id)
371 'net-id': port['port']['network_id'],
372 'port-id': port['port']['id']
375 return map(get_network, port_ids)
380 def start(nova_client, start_retry_interval, private_key_path, **kwargs):
381 server = get_server_by_context(nova_client)
383 if is_external_resource_not_conditionally_created(ctx):
384 ctx.logger.info('Validating external server is started')
385 if server.status != SERVER_STATUS_ACTIVE:
386 raise NonRecoverableError(
387 'Expected external resource server {0} to be in '
388 '"{1}" status'.format(server.id, SERVER_STATUS_ACTIVE))
391 if server.status == SERVER_STATUS_ACTIVE:
392 ctx.logger.info('Server is {0}'.format(server.status))
394 if ctx.node.properties['use_password']:
395 private_key = _get_private_key(private_key_path)
396 ctx.logger.debug('retrieving password for server')
397 password = server.get_password(private_key)
400 return ctx.operation.retry(
401 message='Waiting for server to post generated password',
402 retry_after=start_retry_interval)
404 ctx.instance.runtime_properties[ADMIN_PASSWORD_PROPERTY] = password
405 ctx.logger.info('Server has been set with a password')
407 _set_network_and_ip_runtime_properties(server)
410 server_task_state = getattr(server, OS_EXT_STS_TASK_STATE)
412 if server.status == SERVER_STATUS_SHUTOFF and \
413 server_task_state != SERVER_TASK_STATE_POWERING_ON:
414 ctx.logger.info('Server is in {0} status - starting server...'.format(
415 SERVER_STATUS_SHUTOFF))
417 server_task_state = SERVER_TASK_STATE_POWERING_ON
419 if server.status == SERVER_STATUS_BUILD or \
420 server_task_state == SERVER_TASK_STATE_POWERING_ON:
421 return ctx.operation.retry(
422 message='Waiting for server to be in {0} state but is in {1}:{2} '
423 'state. Retrying...'.format(SERVER_STATUS_ACTIVE,
426 retry_after=start_retry_interval)
428 raise NonRecoverableError(
429 'Unexpected server state {0}:{1}'.format(server.status,
435 def stop(nova_client, **kwargs):
439 Depends on OpenStack implementation, server.stop() might not be supported.
441 if is_external_resource(ctx):
442 ctx.logger.info('Not stopping server since an external server is '
446 server = get_server_by_context(nova_client)
448 if server.status != SERVER_STATUS_SHUTOFF:
449 nova_client.servers.stop(server)
451 ctx.logger.info('Server is already stopped')
456 def delete(nova_client, **kwargs):
457 if not is_external_resource(ctx):
458 ctx.logger.info('deleting server')
459 server = get_server_by_context(nova_client)
460 nova_client.servers.delete(server)
461 _wait_for_server_to_be_deleted(nova_client, server)
463 ctx.logger.info('not deleting server since an external server is '
466 delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS)
469 def _wait_for_server_to_be_deleted(nova_client,
473 timeout = time.time() + timeout
474 while time.time() < timeout:
476 server = nova_client.servers.get(server)
477 ctx.logger.debug('Waiting for server "{}" to be deleted. current'
478 ' status: {}'.format(server.id, server.status))
479 time.sleep(sleep_interval)
480 except nova_exceptions.NotFound:
483 raise RuntimeError('Server {} has not been deleted. waited for {} seconds'
484 .format(server.id, timeout))
487 def get_server_by_context(nova_client):
488 return nova_client.servers.get(
489 ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY])
492 def _set_network_and_ip_runtime_properties(server):
496 if not server.networks:
497 raise NonRecoverableError(
498 'The server was created but not attached to a network. '
499 'Cloudify requires that a server is connected to '
503 manager_network_ip = None
504 management_network_name = server.metadata.get(
505 'cloudify_management_network_name')
507 for network, network_ips in server.networks.items():
508 if (management_network_name and
509 network == management_network_name) or not \
511 manager_network_ip = next(iter(network_ips or []), None)
512 ips[network] = network_ips
513 ctx.instance.runtime_properties[NETWORKS_PROPERTY] = ips
514 # The ip of this instance in the management network
515 ctx.instance.runtime_properties[IP_PROPERTY] = manager_network_ip
520 def connect_floatingip(nova_client, fixed_ip, **kwargs):
521 server_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
522 floating_ip_id = ctx.target.instance.runtime_properties[
523 OPENSTACK_ID_PROPERTY]
525 if is_external_relationship_not_conditionally_created(ctx):
526 ctx.logger.info('Validating external floatingip and server '
528 if nova_client.floating_ips.get(floating_ip_id).instance_id ==\
531 raise NonRecoverableError(
532 'Expected external resources server {0} and floating-ip {1} to be '
533 'connected'.format(server_id, floating_ip_id))
535 floating_ip_address = ctx.target.instance.runtime_properties[
537 server = nova_client.servers.get(server_id)
538 server.add_floating_ip(floating_ip_address, fixed_ip or None)
540 server = nova_client.servers.get(server_id)
541 all_server_ips = reduce(operator.add, server.networks.values())
542 if floating_ip_address not in all_server_ips:
543 return ctx.operation.retry(message='Failed to assign floating ip {0}'
545 .format(floating_ip_address, server_id))
551 def disconnect_floatingip(nova_client, neutron_client, **kwargs):
552 if is_external_relationship(ctx):
553 ctx.logger.info('Not disassociating floatingip and server since '
554 'external floatingip and server are being used')
557 server_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
558 ctx.logger.info("Remove floating ip {0}".format(
559 ctx.target.instance.runtime_properties[IP_ADDRESS_PROPERTY]))
560 server_floating_ip = get_server_floating_ip(neutron_client, server_id)
561 if server_floating_ip:
562 server = nova_client.servers.get(server_id)
563 server.remove_floating_ip(server_floating_ip['floating_ip_address'])
564 ctx.logger.info("Floating ip {0} detached from server"
565 .format(server_floating_ip['floating_ip_address']))
570 def connect_security_group(nova_client, **kwargs):
571 server_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
572 security_group_id = ctx.target.instance.runtime_properties[
573 OPENSTACK_ID_PROPERTY]
574 security_group_name = ctx.target.instance.runtime_properties[
575 OPENSTACK_NAME_PROPERTY]
577 if is_external_relationship_not_conditionally_created(ctx):
578 ctx.logger.info('Validating external security group and server '
580 server = nova_client.servers.get(server_id)
581 if [sg for sg in server.list_security_group() if sg.id ==
584 raise NonRecoverableError(
585 'Expected external resources server {0} and security-group {1} to '
586 'be connected'.format(server_id, security_group_id))
588 server = nova_client.servers.get(server_id)
589 for security_group in server.list_security_group():
590 # Since some security groups are already attached in
591 # create this will ensure that they are not attached twice.
592 if security_group_id != security_group.id and \
593 security_group_name != security_group.name:
594 # to support nova security groups as well,
595 # we connect the security group by name
596 # (as connecting by id
597 # doesn't seem to work well for nova SGs)
598 server.add_security_group(security_group_name)
600 _validate_security_group_and_server_connection_status(nova_client,
609 def disconnect_security_group(nova_client, **kwargs):
610 if is_external_relationship(ctx):
611 ctx.logger.info('Not disconnecting security group and server since '
612 'external security group and server are being used')
615 server_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
616 security_group_id = ctx.target.instance.runtime_properties[
617 OPENSTACK_ID_PROPERTY]
618 security_group_name = ctx.target.instance.runtime_properties[
619 OPENSTACK_NAME_PROPERTY]
620 server = nova_client.servers.get(server_id)
621 # to support nova security groups as well, we disconnect the security group
622 # by name (as disconnecting by id doesn't seem to work well for nova SGs)
623 server.remove_security_group(security_group_name)
625 _validate_security_group_and_server_connection_status(nova_client,
635 def attach_volume(nova_client, cinder_client, status_attempts,
636 status_timeout, **kwargs):
637 server_id = ctx.target.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
638 volume_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
640 if is_external_relationship_not_conditionally_created(ctx):
641 ctx.logger.info('Validating external volume and server '
643 attachment = volume.get_attachment(cinder_client=cinder_client,
649 raise NonRecoverableError(
650 'Expected external resources server {0} and volume {1} to be '
651 'connected'.format(server_id, volume_id))
653 # Note: The 'device_name' property should actually be a property of the
654 # relationship between a server and a volume; It'll move to that
655 # relationship type once relationship properties are better supported.
656 device = ctx.source.node.properties[volume.DEVICE_NAME_PROPERTY]
657 nova_client.volumes.create_server_volume(
660 device if device != 'auto' else None)
662 vol, wait_succeeded = volume.wait_until_status(
663 cinder_client=cinder_client,
665 status=volume.VOLUME_STATUS_IN_USE,
666 num_tries=status_attempts,
667 timeout=status_timeout
669 if not wait_succeeded:
670 raise RecoverableError(
671 'Waiting for volume status {0} failed - detaching volume and '
672 'retrying..'.format(volume.VOLUME_STATUS_IN_USE))
674 # The device name was assigned automatically so we
675 # query the actual device name
676 attachment = volume.get_attachment(
677 cinder_client=cinder_client,
681 device_name = attachment['device']
682 ctx.logger.info('Detected device name for attachment of volume '
683 '{0} to server {1}: {2}'
684 .format(volume_id, server_id, device_name))
685 ctx.source.instance.runtime_properties[
686 volume.DEVICE_NAME_PROPERTY] = device_name
688 if not isinstance(e, NonRecoverableError):
689 _prepare_attach_volume_to_be_repeated(
690 nova_client, cinder_client, server_id, volume_id,
691 status_attempts, status_timeout)
695 def _prepare_attach_volume_to_be_repeated(
696 nova_client, cinder_client, server_id, volume_id,
697 status_attempts, status_timeout):
699 ctx.logger.info('Cleaning after a failed attach_volume() call')
701 _detach_volume(nova_client, cinder_client, server_id, volume_id,
702 status_attempts, status_timeout)
704 ctx.logger.error('Cleaning after a failed attach_volume() call failed '
705 'raising a \'{0}\' exception.'.format(e))
706 raise NonRecoverableError(e)
709 def _detach_volume(nova_client, cinder_client, server_id, volume_id,
710 status_attempts, status_timeout):
711 attachment = volume.get_attachment(cinder_client=cinder_client,
715 nova_client.volumes.delete_server_volume(server_id, attachment['id'])
716 volume.wait_until_status(cinder_client=cinder_client,
718 status=volume.VOLUME_STATUS_AVAILABLE,
719 num_tries=status_attempts,
720 timeout=status_timeout)
726 def detach_volume(nova_client, cinder_client, status_attempts,
727 status_timeout, **kwargs):
728 if is_external_relationship(ctx):
729 ctx.logger.info('Not detaching volume from server since '
730 'external volume and server are being used')
733 server_id = ctx.target.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
734 volume_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
736 _detach_volume(nova_client, cinder_client, server_id, volume_id,
737 status_attempts, status_timeout)
740 def _fail_on_missing_required_parameters(obj, required_parameters, hint_where):
741 for k in required_parameters:
743 raise NonRecoverableError(
744 "Required parameter '{0}' is missing (under host's "
745 "properties.{1}). Required parameters are: {2}"
746 .format(k, hint_where, required_parameters))
749 def _validate_external_server_keypair(nova_client):
750 keypair_id = get_openstack_id_of_single_connected_node_by_openstack_type(
751 ctx, KEYPAIR_OPENSTACK_TYPE, True)
755 keypair_instance_id = \
756 [node_instance_id for node_instance_id, runtime_props in
757 ctx.capabilities.get_all().iteritems() if
758 runtime_props.get(OPENSTACK_ID_PROPERTY) == keypair_id][0]
759 keypair_node_properties = _get_properties_by_node_instance_id(
761 if not is_external_resource_by_properties(keypair_node_properties):
762 raise NonRecoverableError(
763 "Can't connect a new keypair node to a server node "
764 "with '{0}'=True".format(USE_EXTERNAL_RESOURCE_PROPERTY))
766 server = get_server_by_context(nova_client)
767 if keypair_id == _get_keypair_name_by_id(nova_client, server.key_name):
769 raise NonRecoverableError(
770 "Expected external resources server {0} and keypair {1} to be "
771 "connected".format(server.id, keypair_id))
774 def _get_keypair_name_by_id(nova_client, key_name):
775 keypair = nova_client.cosmo_get_named(KEYPAIR_OPENSTACK_TYPE, key_name)
779 def _validate_external_server_nics(neutron_client, network_ids, port_ids):
780 # validate no new nics are being assigned to an existing server (which
781 # isn't possible on Openstack)
783 [node_instance_id for node_instance_id, runtime_props in
784 ctx.capabilities.get_all().iteritems() if runtime_props.get(
785 OPENSTACK_TYPE_PROPERTY) in (PORT_OPENSTACK_TYPE,
786 NETWORK_OPENSTACK_TYPE) and
787 not is_external_resource_by_properties(
788 _get_properties_by_node_instance_id(node_instance_id))]
790 raise NonRecoverableError(
791 "Can't connect new port and/or network nodes to a server node "
792 "with '{0}'=True".format(USE_EXTERNAL_RESOURCE_PROPERTY))
794 # validate all expected connected networks and ports are indeed already
795 # connected to the server. note that additional networks (e.g. the
796 # management network) may be connected as well with no error raised
797 if not network_ids and not port_ids:
800 server_id = ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY]
801 connected_ports = neutron_client.list_ports(device_id=server_id)['ports']
803 # not counting networks connected by a connected port since allegedly
804 # the connection should be on a separate port
805 connected_ports_networks = {port['network_id'] for port in
806 connected_ports if port['id'] not in port_ids}
807 connected_ports_ids = {port['id'] for port in
809 disconnected_networks = [network_id for network_id in network_ids if
810 network_id not in connected_ports_networks]
811 disconnected_ports = [port_id for port_id in port_ids if port_id not
812 in connected_ports_ids]
813 if disconnected_networks or disconnected_ports:
814 raise NonRecoverableError(
815 'Expected external resources to be connected to external server {'
816 '0}: Networks - {1}; Ports - {2}'.format(server_id,
817 disconnected_networks,
821 def _get_properties_by_node_instance_id(node_instance_id):
822 client = get_rest_client()
823 node_instance = client.node_instances.get(node_instance_id)
824 node = client.nodes.get(ctx.deployment.id, node_instance.node_id)
825 return node.properties
830 def creation_validation(nova_client, args, **kwargs):
832 def validate_server_property_value_exists(server_props, property_name):
834 'checking whether {0} exists...'.format(property_name))
836 serv_props_copy = server_props.copy()
838 handle_image_from_relationship(serv_props_copy, 'image', ctx)
839 _handle_image_or_flavor(serv_props_copy, nova_client,
841 except (NonRecoverableError, nova_exceptions.NotFound) as e:
842 # temporary error - once image/flavor_name get removed, these
843 # errors won't be relevant anymore
845 ctx.logger.error('VALIDATION ERROR: ' + err)
846 raise NonRecoverableError(err)
848 prop_value_id = str(serv_props_copy[property_name])
849 prop_values = list(nova_client.cosmo_list(property_name))
850 for f in prop_values:
851 if prop_value_id == f.id:
852 ctx.logger.debug('OK: {0} exists'.format(property_name))
854 err = '{0} {1} does not exist'.format(property_name, prop_value_id)
855 ctx.logger.error('VALIDATION ERROR: ' + err)
857 ctx.logger.info('list of available {0}s:'.format(property_name))
858 for f in prop_values:
859 ctx.logger.info(' {0:>10} - {1}'.format(f.id, f.name))
861 ctx.logger.info('there are no available {0}s'.format(
863 raise NonRecoverableError(err)
865 validate_resource(ctx, nova_client, SERVER_OPENSTACK_TYPE)
867 server_props = dict(ctx.node.properties['server'], **args)
868 validate_server_property_value_exists(server_props, 'flavor')
871 def _get_private_key(private_key_path):
873 get_single_connected_node_by_openstack_type(
874 ctx, KEYPAIR_OPENSTACK_TYPE, True)
878 raise NonRecoverableError("server can't both have a "
879 '"private_key_path" input and be '
880 'connected to a keypair via a '
881 'relationship at the same time')
882 key_path = private_key_path
884 if pk_node_by_rel and pk_node_by_rel.properties['private_key_path']:
885 key_path = pk_node_by_rel.properties['private_key_path']
887 key_path = ctx.bootstrap_context.cloudify_agent.agent_key_path
890 key_path = os.path.expanduser(key_path)
891 if os.path.isfile(key_path):
894 err_message = 'Cannot find private key file'
896 err_message += '; expected file path was {0}'.format(key_path)
897 raise NonRecoverableError(err_message)
900 def _validate_security_group_and_server_connection_status(
901 nova_client, server_id, sg_id, sg_name, is_connected):
903 # verifying the security group got connected or disconnected
904 # successfully - this is due to Openstack concurrency issues that may
905 # take place when attempting to connect/disconnect multiple SGs to the
906 # same server at the same time
907 server = nova_client.servers.get(server_id)
909 if is_connected ^ any(sg for sg in server.list_security_group() if
911 raise RecoverableError(
912 message='Security group {0} did not get {2} server {1} '
917 'connected to' if is_connected else 'disconnected from'))
920 def _handle_image_or_flavor(server, nova_client, prop_name):
921 if prop_name not in server and '{0}_name'.format(prop_name) not in server:
922 # setting image or flavor - looking it up by name; if not found, then
923 # the value is assumed to be the id
924 server[prop_name] = ctx.node.properties[prop_name]
926 # temporary error message: once the 'image' and 'flavor' properties
927 # become mandatory, this will become less relevant
928 if not server[prop_name]:
929 raise NonRecoverableError(
930 'must set {0} by either setting a "{0}" property or by setting'
931 ' a "{0}" or "{0}_name" (deprecated) field under the "server" '
932 'property'.format(prop_name))
935 nova_client.cosmo_get_if_exists(prop_name, name=server[prop_name])
937 server[prop_name] = image_or_flavor.id
938 else: # Deprecated sugar
939 if '{0}_name'.format(prop_name) in server:
940 prop_name_plural = nova_client.cosmo_plural(prop_name)
941 server[prop_name] = \
942 getattr(nova_client, prop_name_plural).find(
943 name=server['{0}_name'.format(prop_name)]).id
944 del server['{0}_name'.format(prop_name)]