Coverage for plugins/modules/purefb_network.py: 64%

83 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-10-30 17:06 -0400

1#!/usr/bin/python 

2# -*- coding: utf-8 -*- 

3 

4# (c) 2017, Simon Dodsley (simon@everpuredata.com) 

5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 

6 

7from __future__ import absolute_import, division, print_function 

8 

9__metaclass__ = type 

10 

11 

12ANSIBLE_METADATA = { 

13 "metadata_version": "1.1", 

14 "status": ["preview"], 

15 "supported_by": "community", 

16} 

17 

18 

19DOCUMENTATION = """ 

20--- 

21module: purefb_network 

22version_added: "1.0.0" 

23short_description: Manage network interfaces in a Everpure FlashBlade 

24description: 

25 - This module manages network interfaces on Everpure FlashBlade. 

26 - When creating a network interface a subnet must already exist with 

27 a network prefix that covers the IP address of the interface being 

28 created. 

29author: Everpure Ansible Team (@sdodsley) <pure-ansible-team@everpuredata.com> 

30options: 

31 name: 

32 description: 

33 - Interface Name. 

34 required: true 

35 type: str 

36 state: 

37 description: 

38 - Create, delete or modifies a network interface. 

39 required: false 

40 default: present 

41 choices: [ "present", "absent" ] 

42 type: str 

43 address: 

44 description: 

45 - IP address of interface. 

46 required: false 

47 type: str 

48 services: 

49 description: 

50 - Define which services are configured for the interfaces. 

51 required: false 

52 choices: [ "data", "replication" ] 

53 default: data 

54 type: str 

55 itype: 

56 description: 

57 - Type of interface. 

58 required: false 

59 choices: [ "vip" ] 

60 default: vip 

61 type: str 

62 attached_server: 

63 description: 

64 - Name of the server you want to attach to the interface 

65 - To attach to a server on a Realm, set the value to realm-1::server-1 

66 - Only 1 server per Network Interface. 

67 required: false 

68 type: str 

69 

70extends_documentation_fragment: 

71 - everpure.flashblade.everpure.fb 

72""" 

73 

74EXAMPLES = """ 

75- name: Create new network interface named foo 

76 everpure.flashblade.purefb_network: 

77 name: foo 

78 address: 10.21.200.23 

79 state: present 

80 fb_url: 10.10.10.2 

81 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 

82 

83- name: Create a new network interface named foo with attached server named server-1 in a Realm named realm-1 

84 everpure.flashblade.purefb_network: 

85 name: foo 

86 address: 10.21.200.23 

87 attached_server: realm-1::server-1 

88 state: present 

89 fb_url: 10.10.10.2 

90 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 

91 

92- name: Change IP address of network interface named foo 

93 everpure.flashblade.purefb_network: 

94 name: foo 

95 state: present 

96 address: 10.21.200.123 

97 fb_url: 10.10.10.2 

98 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 

99 

100- name: Delete network interface named foo 

101 everpure.flashblade.purefb_network: 

102 name: foo 

103 state: absent 

104 fb_url: 10.10.10.2 

105 api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641 

106""" 

107 

108RETURN = """ 

109""" 

110 

111HAS_PYPURECLIENT = True 

112try: 

113 from pypureclient.flashblade import NetworkInterface, NetworkInterfacePatch 

114except ImportError: 

115 HAS_PYPURECLIENT = False 

116 

117from ansible.module_utils.basic import AnsibleModule 

118from ansible_collections.everpure.flashblade.plugins.module_utils.purefb import ( 

119 get_system, 

120 purefb_argument_spec, 

121) 

122from ansible_collections.everpure.flashblade.plugins.module_utils.common import ( 

123 get_error_message, 

124 get_rest_api_version, 

125) 

126from ansible_collections.everpure.flashblade.plugins.module_utils.version import ( 

127 LooseVersion, 

128) 

129 

130SERVERS_API_VERSION = "2.16" 

131 

132 

133def get_iface(module, blade): 

134 """Return Filesystem or None""" 

135 res = blade.get_network_interfaces(names=[module.params["name"]]) 

136 if res.status_code == 200: 

137 return list(res.items)[0] 

138 return None 

139 

140 

141def _attached_server_name(iface): 

142 """Name of the server an interface is attached to, or None. 

143 

144 ``attached_servers`` is a list of references on the API, even though an 

145 interface can only carry one server. 

146 """ 

147 attached = getattr(iface, "attached_servers", None) or [] 

148 if not attached: 

149 return None 

150 return getattr(attached[0], "name", None) 

151 

152 

153def create_iface(module, blade): 

154 """Create Network Interface""" 

155 changed = True 

156 if not module.check_mode: 

157 network_interface = NetworkInterface( 

158 address=module.params["address"], 

159 services=[module.params["services"]], 

160 type=module.params["itype"], 

161 ) 

162 if module.params["attached_server"]: 

163 network_interface.attached_servers = [ 

164 {"name": module.params["attached_server"]} 

165 ] 

166 res = blade.post_network_interfaces( 

167 names=[module.params["name"]], 

168 network_interface=network_interface, 

169 ) 

170 if res.status_code != 200: 

171 module.fail_json( 

172 msg="Interface {0} creation failed. Error: {1}".format( 

173 module.params["name"], get_error_message(res) 

174 ) 

175 ) 

176 module.exit_json(changed=changed) 

177 

178 

179def modify_iface(module, blade): 

180 """Modify Network Interface IP address""" 

181 changed = False 

182 iface = get_iface(module, blade) 

183 if module.params["address"] != iface.address: 

184 changed = True 

185 if not module.check_mode: 

186 res = blade.patch_network_interfaces( 

187 names=[module.params["name"]], 

188 network_interface=NetworkInterfacePatch( 

189 address=module.params["address"] 

190 ), 

191 ) 

192 if res.status_code != 200: 

193 module.fail_json( 

194 msg="Failed to modify Interface {0}. Error: {1}".format( 

195 module.params["name"], get_error_message(res) 

196 ) 

197 ) 

198 # Only move the interface when it is not already on the requested server. 

199 # An interface carries at most one attached server, but the API field is 

200 # a list, so the current name has to be pulled out of it. 

201 wanted_server = module.params["attached_server"] 

202 if wanted_server is not None and wanted_server != _attached_server_name(iface): 

203 changed = True 

204 if not module.check_mode: 

205 res = blade.patch_network_interfaces( 

206 names=[module.params["name"]], 

207 network_interface=NetworkInterfacePatch( 

208 attached_servers=[{"name": module.params["attached_server"]}] 

209 ), 

210 ) 

211 if res.status_code != 200: 

212 module.fail_json( 

213 msg="Failed to modify Interface {0}. Error: {1}".format( 

214 module.params["name"], get_error_message(res) 

215 ) 

216 ) 

217 module.exit_json(changed=changed) 

218 

219 

220def delete_iface(module, blade): 

221 """Delete Network Interface""" 

222 changed = True 

223 if not module.check_mode: 

224 res = blade.delete_network_interfaces(names=[module.params["name"]]) 

225 if res.status_code != 200: 

226 module.fail_json( 

227 msg="Failed to delete network {0}. Error: {1}".format( 

228 module.params["name"], get_error_message(res) 

229 ) 

230 ) 

231 module.exit_json(changed=changed) 

232 

233 

234def main(): 

235 argument_spec = purefb_argument_spec() 

236 argument_spec.update( 

237 dict( 

238 name=dict(type="str", required=True), 

239 state=dict(type="str", default="present", choices=["present", "absent"]), 

240 address=dict(type="str"), 

241 services=dict(type="str", default="data", choices=["data", "replication"]), 

242 itype=dict(type="str", default="vip", choices=["vip"]), 

243 attached_server=dict(type="str", required=False), 

244 ) 

245 ) 

246 

247 required_if = [["state", "present", ["address"]]] 

248 

249 module = AnsibleModule( 

250 argument_spec, required_if=required_if, supports_check_mode=True 

251 ) 

252 

253 if not HAS_PYPURECLIENT: 

254 module.fail_json(msg="py-pure-client sdk is required for this module") 

255 state = module.params["state"] 

256 blade = get_system(module) 

257 iface = get_iface(module, blade) 

258 

259 api_version = get_rest_api_version(blade) 

260 if module.params["attached_server"] and LooseVersion( 

261 SERVERS_API_VERSION 

262 ) > LooseVersion(api_version): 

263 module.fail_json( 

264 msg="FlashBlade REST version {0} does not support attached_server " 

265 "(requires {1}+).".format(api_version, SERVERS_API_VERSION) 

266 ) 

267 

268 if state == "present" and not iface: 

269 create_iface(module, blade) 

270 elif state == "present" and iface: 

271 modify_iface(module, blade) 

272 elif state == "absent" and iface: 

273 delete_iface(module, blade) 

274 elif state == "absent" and not iface: 

275 module.exit_json(changed=False) 

276 

277 

278if __name__ == "__main__": 

279 main()