Coverage for nova/virt/ironic/patcher.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2014 Hewlett-Packard Development Company, L.P. 

2# Copyright 2014 Red Hat, Inc. 

3# All Rights Reserved. 

4# 

5# Licensed under the Apache License, Version 2.0 (the "License"); you may 

6# not use this file except in compliance with the License. You may obtain 

7# a copy of the License at 

8# 

9# http://www.apache.org/licenses/LICENSE-2.0 

10# 

11# Unless required by applicable law or agreed to in writing, software 

12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

14# License for the specific language governing permissions and limitations 

15# under the License. 

16# 

17""" 

18Helper classes for Ironic HTTP PATCH creation. 

19""" 

20 

21from oslo_serialization import jsonutils 

22 

23 

24import nova.conf 

25 

26CONF = nova.conf.CONF 

27 

28 

29def create(node): 

30 """Create an instance of the appropriate DriverFields class. 

31 

32 :param node: a node object returned from openstacksdk 

33 :returns: A GenericDriverFields instance. 

34 """ 

35 return GenericDriverFields(node) 

36 

37 

38class GenericDriverFields(object): 

39 

40 def __init__(self, node): 

41 self.node = node 

42 

43 def get_deploy_patch(self, instance, image_meta, flavor, metadata, 

44 preserve_ephemeral=None, boot_from_volume=False): 

45 """Build a patch to add the required fields to deploy a node. 

46 

47 :param instance: the instance object. 

48 :param image_meta: the nova.objects.ImageMeta object instance 

49 :param flavor: the flavor object. 

50 :param metadata: nova.virt.driver.InstanceDriverMetadata dataclass 

51 :param preserve_ephemeral: preserve_ephemeral status (bool) to be 

52 specified during rebuild. 

53 :param boot_from_volume: True if node boots from volume. Then, 

54 image_source is not passed to ironic. 

55 :returns: a json-patch with the fields that needs to be updated. 

56 

57 """ 

58 patch = [] 

59 if not boot_from_volume: 

60 patch.append({'path': '/instance_info/image_source', 'op': 'add', 

61 'value': image_meta.id}) 

62 patch.append({'path': '/instance_info/root_gb', 'op': 'add', 

63 'value': str(instance.flavor.root_gb)}) 

64 patch.append({'path': '/instance_info/swap_mb', 'op': 'add', 

65 'value': str(flavor['swap'])}) 

66 patch.append({'path': '/instance_info/display_name', 

67 'op': 'add', 'value': instance.display_name}) 

68 patch.append({'path': '/instance_info/vcpus', 'op': 'add', 

69 'value': str(instance.flavor.vcpus)}) 

70 patch.append({'path': '/instance_info/nova_host_id', 'op': 'add', 

71 'value': instance.get('host')}) 

72 patch.append({'path': '/instance_info/memory_mb', 'op': 'add', 

73 'value': str(instance.flavor.memory_mb)}) 

74 patch.append({'path': '/instance_info/local_gb', 'op': 'add', 

75 'value': str(self.node.properties.get('local_gb', 0))}) 

76 

77 patch.append({'path': '/instance_info/project_id', 'op': 'add', 

78 'value': str(metadata.owner.projectid)}) 

79 patch.append({'path': '/instance_info/project_name', 'op': 'add', 

80 'value': str(metadata.owner.projectname)}) 

81 patch.append({'path': '/instance_info/user_id', 'op': 'add', 

82 'value': str(metadata.owner.userid)}) 

83 patch.append({'path': '/instance_info/user_name', 'op': 'add', 

84 'value': str(metadata.owner.username)}) 

85 patch.append({'path': '/instance_info/flavor_name', 'op': 'add', 

86 'value': str(metadata.flavor.name)}) 

87 patch.append({'path': '/instance_info/fixed_ips', 'op': 'add', 

88 'value': str(metadata.network_info.fixed_ips())}) 

89 patch.append({'path': '/instance_info/floating_ips', 'op': 'add', 

90 'value': str(metadata.network_info.floating_ips())}) 

91 

92 if instance.flavor.ephemeral_gb: 

93 patch.append({'path': '/instance_info/ephemeral_gb', 

94 'op': 'add', 

95 'value': str(instance.flavor.ephemeral_gb)}) 

96 if CONF.default_ephemeral_format: 

97 patch.append({'path': '/instance_info/ephemeral_format', 

98 'op': 'add', 

99 'value': CONF.default_ephemeral_format}) 

100 

101 if preserve_ephemeral is not None: 

102 patch.append({'path': '/instance_info/preserve_ephemeral', 

103 'op': 'add', 'value': str(preserve_ephemeral)}) 

104 

105 # read the flavor and get the extra_specs value. 

106 extra_specs = flavor.get('extra_specs') 

107 

108 # scan through the extra_specs values and ignore the keys 

109 # not starting with keyword 'capabilities' and 'trait' 

110 capabilities = {} 

111 traits = [] 

112 for key, val in extra_specs.items(): 

113 # NOTE(mgoddard): For traits we need to support granular resource 

114 # request syntax, where the 'trait' prefix may be followed by a 

115 # numeric suffix: trait$N. For ironic we do not care about the 

116 # group number. 

117 if key.startswith('capabilities:') or key.startswith('trait'): 

118 # get the actual key. 

119 prefix, parsed_key = key.split(':', 1) 

120 if prefix == "capabilities": 

121 capabilities[parsed_key] = val 

122 else: 

123 # NOTE(mgoddard): Currently, the value must be 'required'. 

124 # We do not need to pass the value to ironic. When the 

125 # value can be something other than 'required', we may need 

126 # to filter out traits not supported by the node. 

127 if val == 'required': 

128 traits.append(parsed_key) 

129 traits.extend(image_meta.properties.get('traits_required', [])) 

130 if capabilities: 

131 patch.append({'path': '/instance_info/capabilities', 

132 'op': 'add', 'value': jsonutils.dumps(capabilities)}) 

133 if traits: 

134 # NOTE(mgoddard): Don't JSON encode the traits list - ironic 

135 # expects instance_info.traits to be a list. 

136 patch.append({'path': '/instance_info/traits', 

137 'op': 'add', 'value': traits}) 

138 return patch