1#!/usr/bin/python 2 3# Copyright (c) 2018 Catalyst Cloud Ltd. 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import absolute_import, division, print_function 7 8__metaclass__ = type 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = ''' 15--- 16module: os_loadbalancer 17short_description: Add/Delete load balancer from OpenStack Cloud 18extends_documentation_fragment: openstack 19version_added: "2.7" 20author: "Lingxian Kong (@lingxiankong)" 21description: 22 - Add or Remove load balancer from the OpenStack load-balancer 23 service(Octavia). Load balancer update is not supported for now. 24options: 25 name: 26 description: 27 - Name that has to be given to the load balancer 28 required: true 29 state: 30 description: 31 - Should the resource be present or absent. 32 choices: [present, absent] 33 default: present 34 vip_network: 35 description: 36 - The name or id of the network for the virtual IP of the load balancer. 37 One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified 38 for creation. 39 vip_subnet: 40 description: 41 - The name or id of the subnet for the virtual IP of the load balancer. 42 One of I(vip_network), I(vip_subnet), or I(vip_port) must be specified 43 for creation. 44 vip_port: 45 description: 46 - The name or id of the load balancer virtual IP port. One of 47 I(vip_network), I(vip_subnet), or I(vip_port) must be specified for 48 creation. 49 vip_address: 50 description: 51 - IP address of the load balancer virtual IP. 52 public_ip_address: 53 description: 54 - Public IP address associated with the VIP. 55 auto_public_ip: 56 description: 57 - Allocate a public IP address and associate with the VIP automatically. 58 type: bool 59 default: 'no' 60 public_network: 61 description: 62 - The name or ID of a Neutron external network. 63 delete_public_ip: 64 description: 65 - When C(state=absent) and this option is true, any public IP address 66 associated with the VIP will be deleted along with the load balancer. 67 type: bool 68 default: 'no' 69 listeners: 70 description: 71 - A list of listeners that attached to the load balancer. 72 suboptions: 73 name: 74 description: 75 - The listener name or ID. 76 protocol: 77 description: 78 - The protocol for the listener. 79 default: HTTP 80 protocol_port: 81 description: 82 - The protocol port number for the listener. 83 default: 80 84 pool: 85 description: 86 - The pool attached to the listener. 87 suboptions: 88 name: 89 description: 90 - The pool name or ID. 91 protocol: 92 description: 93 - The protocol for the pool. 94 default: HTTP 95 lb_algorithm: 96 description: 97 - The load balancing algorithm for the pool. 98 default: ROUND_ROBIN 99 members: 100 description: 101 - A list of members that added to the pool. 102 suboptions: 103 name: 104 description: 105 - The member name or ID. 106 address: 107 description: 108 - The IP address of the member. 109 protocol_port: 110 description: 111 - The protocol port number for the member. 112 default: 80 113 subnet: 114 description: 115 - The name or ID of the subnet the member service is 116 accessible from. 117 wait: 118 description: 119 - If the module should wait for the load balancer to be created or 120 deleted. 121 type: bool 122 default: 'yes' 123 timeout: 124 description: 125 - The amount of time the module should wait. 126 default: 180 127 availability_zone: 128 description: 129 - Ignored. Present for backwards compatibility 130requirements: ["openstacksdk"] 131''' 132 133RETURN = ''' 134id: 135 description: The load balancer UUID. 136 returned: On success when C(state=present) 137 type: str 138 sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" 139loadbalancer: 140 description: Dictionary describing the load balancer. 141 returned: On success when C(state=present) 142 type: complex 143 contains: 144 id: 145 description: Unique UUID. 146 type: str 147 sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69" 148 name: 149 description: Name given to the load balancer. 150 type: str 151 sample: "lingxian_test" 152 vip_network_id: 153 description: Network ID the load balancer virtual IP port belongs in. 154 type: str 155 sample: "f171db43-56fd-41cf-82d7-4e91d741762e" 156 vip_subnet_id: 157 description: Subnet ID the load balancer virtual IP port belongs in. 158 type: str 159 sample: "c53e3c70-9d62-409a-9f71-db148e7aa853" 160 vip_port_id: 161 description: The load balancer virtual IP port ID. 162 type: str 163 sample: "2061395c-1c01-47ab-b925-c91b93df9c1d" 164 vip_address: 165 description: The load balancer virtual IP address. 166 type: str 167 sample: "192.168.2.88" 168 public_vip_address: 169 description: The load balancer public VIP address. 170 type: str 171 sample: "10.17.8.254" 172 provisioning_status: 173 description: The provisioning status of the load balancer. 174 type: str 175 sample: "ACTIVE" 176 operating_status: 177 description: The operating status of the load balancer. 178 type: str 179 sample: "ONLINE" 180 is_admin_state_up: 181 description: The administrative state of the load balancer. 182 type: bool 183 sample: true 184 listeners: 185 description: The associated listener IDs, if any. 186 type: list 187 sample: [{"id": "7aa1b380-beec-459c-a8a7-3a4fb6d30645"}, {"id": "692d06b8-c4f8-4bdb-b2a3-5a263cc23ba6"}] 188 pools: 189 description: The associated pool IDs, if any. 190 type: list 191 sample: [{"id": "27b78d92-cee1-4646-b831-e3b90a7fa714"}, {"id": "befc1fb5-1992-4697-bdb9-eee330989344"}] 192''' 193 194EXAMPLES = ''' 195# Create a load balancer by specifying the VIP subnet. 196- os_loadbalancer: 197 auth: 198 auth_url: https://identity.example.com 199 username: admin 200 password: passme 201 project_name: admin 202 state: present 203 name: my_lb 204 vip_subnet: my_subnet 205 timeout: 150 206 207# Create a load balancer by specifying the VIP network and the IP address. 208- os_loadbalancer: 209 auth: 210 auth_url: https://identity.example.com 211 username: admin 212 password: passme 213 project_name: admin 214 state: present 215 name: my_lb 216 vip_network: my_network 217 vip_address: 192.168.0.11 218 219# Create a load balancer together with its sub-resources in the 'all in one' 220# way. A public IP address is also allocated to the load balancer VIP. 221- os_loadbalancer: 222 auth: 223 auth_url: https://identity.example.com 224 username: admin 225 password: passme 226 project_name: admin 227 name: lingxian_test 228 state: present 229 vip_subnet: kong_subnet 230 auto_public_ip: yes 231 public_network: public 232 listeners: 233 - name: lingxian_80 234 protocol: TCP 235 protocol_port: 80 236 pool: 237 name: lingxian_80_pool 238 protocol: TCP 239 members: 240 - name: mywebserver1 241 address: 192.168.2.81 242 protocol_port: 80 243 subnet: webserver_subnet 244 - name: lingxian_8080 245 protocol: TCP 246 protocol_port: 8080 247 pool: 248 name: lingxian_8080-pool 249 protocol: TCP 250 members: 251 - name: mywebserver2 252 address: 192.168.2.82 253 protocol_port: 8080 254 wait: yes 255 timeout: 600 256 257# Delete a load balancer(and all its related resources) 258- os_loadbalancer: 259 auth: 260 auth_url: https://identity.example.com 261 username: admin 262 password: passme 263 project_name: admin 264 state: absent 265 name: my_lb 266 267# Delete a load balancer(and all its related resources) together with the 268# public IP address(if any) attached to it. 269- os_loadbalancer: 270 auth: 271 auth_url: https://identity.example.com 272 username: admin 273 password: passme 274 project_name: admin 275 state: absent 276 name: my_lb 277 delete_public_ip: yes 278''' 279 280import time 281 282from ansible.module_utils.basic import AnsibleModule 283from ansible.module_utils.openstack import openstack_full_argument_spec, \ 284 openstack_module_kwargs, openstack_cloud_from_module 285 286 287def _wait_for_lb(module, cloud, lb, status, failures, interval=5): 288 """Wait for load balancer to be in a particular provisioning status.""" 289 timeout = module.params['timeout'] 290 291 total_sleep = 0 292 if failures is None: 293 failures = [] 294 295 while total_sleep < timeout: 296 lb = cloud.load_balancer.find_load_balancer(lb.id) 297 298 if lb: 299 if lb.provisioning_status == status: 300 return None 301 if lb.provisioning_status in failures: 302 module.fail_json( 303 msg="Load Balancer %s transitioned to failure state %s" % 304 (lb.id, lb.provisioning_status) 305 ) 306 else: 307 if status == "DELETED": 308 return None 309 else: 310 module.fail_json( 311 msg="Load Balancer %s transitioned to DELETED" % lb.id 312 ) 313 314 time.sleep(interval) 315 total_sleep += interval 316 317 module.fail_json( 318 msg="Timeout waiting for Load Balancer %s to transition to %s" % 319 (lb.id, status) 320 ) 321 322 323def main(): 324 argument_spec = openstack_full_argument_spec( 325 name=dict(required=True), 326 state=dict(default='present', choices=['absent', 'present']), 327 vip_network=dict(required=False), 328 vip_subnet=dict(required=False), 329 vip_port=dict(required=False), 330 vip_address=dict(required=False), 331 listeners=dict(type='list', default=[]), 332 public_ip_address=dict(required=False, default=None), 333 auto_public_ip=dict(required=False, default=False, type='bool'), 334 public_network=dict(required=False), 335 delete_public_ip=dict(required=False, default=False, type='bool'), 336 ) 337 module_kwargs = openstack_module_kwargs() 338 module = AnsibleModule(argument_spec, **module_kwargs) 339 sdk, cloud = openstack_cloud_from_module(module) 340 341 vip_network = module.params['vip_network'] 342 vip_subnet = module.params['vip_subnet'] 343 vip_port = module.params['vip_port'] 344 listeners = module.params['listeners'] 345 public_vip_address = module.params['public_ip_address'] 346 allocate_fip = module.params['auto_public_ip'] 347 delete_fip = module.params['delete_public_ip'] 348 public_network = module.params['public_network'] 349 350 vip_network_id = None 351 vip_subnet_id = None 352 vip_port_id = None 353 354 try: 355 changed = False 356 lb = cloud.load_balancer.find_load_balancer( 357 name_or_id=module.params['name']) 358 359 if module.params['state'] == 'present': 360 if not lb: 361 if not (vip_network or vip_subnet or vip_port): 362 module.fail_json( 363 msg="One of vip_network, vip_subnet, or vip_port must " 364 "be specified for load balancer creation" 365 ) 366 367 if vip_network: 368 network = cloud.get_network(vip_network) 369 if not network: 370 module.fail_json( 371 msg='network %s is not found' % vip_network 372 ) 373 vip_network_id = network.id 374 if vip_subnet: 375 subnet = cloud.get_subnet(vip_subnet) 376 if not subnet: 377 module.fail_json( 378 msg='subnet %s is not found' % vip_subnet 379 ) 380 vip_subnet_id = subnet.id 381 if vip_port: 382 port = cloud.get_port(vip_port) 383 if not port: 384 module.fail_json( 385 msg='port %s is not found' % vip_port 386 ) 387 vip_port_id = port.id 388 389 lb = cloud.load_balancer.create_load_balancer( 390 name=module.params['name'], 391 vip_network_id=vip_network_id, 392 vip_subnet_id=vip_subnet_id, 393 vip_port_id=vip_port_id, 394 vip_address=module.params['vip_address'], 395 ) 396 changed = True 397 398 if not listeners and not module.params['wait']: 399 module.exit_json( 400 changed=changed, 401 loadbalancer=lb.to_dict(), 402 id=lb.id 403 ) 404 405 _wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"]) 406 407 for listener_def in listeners: 408 listener_name = listener_def.get("name") 409 pool_def = listener_def.get("pool") 410 411 if not listener_name: 412 module.fail_json(msg='listener name is required') 413 414 listener = cloud.load_balancer.find_listener( 415 name_or_id=listener_name 416 ) 417 418 if not listener: 419 _wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"]) 420 421 protocol = listener_def.get("protocol", "HTTP") 422 protocol_port = listener_def.get("protocol_port", 80) 423 424 listener = cloud.load_balancer.create_listener( 425 name=listener_name, 426 loadbalancer_id=lb.id, 427 protocol=protocol, 428 protocol_port=protocol_port, 429 ) 430 changed = True 431 432 # Ensure pool in the listener. 433 if pool_def: 434 pool_name = pool_def.get("name") 435 members = pool_def.get('members', []) 436 437 if not pool_name: 438 module.fail_json(msg='pool name is required') 439 440 pool = cloud.load_balancer.find_pool(name_or_id=pool_name) 441 442 if not pool: 443 _wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"]) 444 445 protocol = pool_def.get("protocol", "HTTP") 446 lb_algorithm = pool_def.get("lb_algorithm", 447 "ROUND_ROBIN") 448 449 pool = cloud.load_balancer.create_pool( 450 name=pool_name, 451 listener_id=listener.id, 452 protocol=protocol, 453 lb_algorithm=lb_algorithm 454 ) 455 changed = True 456 457 # Ensure members in the pool 458 for member_def in members: 459 member_name = member_def.get("name") 460 if not member_name: 461 module.fail_json(msg='member name is required') 462 463 member = cloud.load_balancer.find_member(member_name, 464 pool.id) 465 466 if not member: 467 _wait_for_lb(module, cloud, lb, "ACTIVE", 468 ["ERROR"]) 469 470 address = member_def.get("address") 471 if not address: 472 module.fail_json( 473 msg='member address for member %s is ' 474 'required' % member_name 475 ) 476 477 subnet_id = member_def.get("subnet") 478 if subnet_id: 479 subnet = cloud.get_subnet(subnet_id) 480 if not subnet: 481 module.fail_json( 482 msg='subnet %s for member %s is not ' 483 'found' % (subnet_id, member_name) 484 ) 485 subnet_id = subnet.id 486 487 protocol_port = member_def.get("protocol_port", 80) 488 489 member = cloud.load_balancer.create_member( 490 pool, 491 name=member_name, 492 address=address, 493 protocol_port=protocol_port, 494 subnet_id=subnet_id 495 ) 496 changed = True 497 498 # Associate public ip to the load balancer VIP. If 499 # public_vip_address is provided, use that IP, otherwise, either 500 # find an available public ip or create a new one. 501 fip = None 502 orig_public_ip = None 503 new_public_ip = None 504 if public_vip_address or allocate_fip: 505 ips = cloud.network.ips( 506 port_id=lb.vip_port_id, 507 fixed_ip_address=lb.vip_address 508 ) 509 ips = list(ips) 510 if ips: 511 orig_public_ip = ips[0] 512 new_public_ip = orig_public_ip.floating_ip_address 513 514 if public_vip_address and public_vip_address != orig_public_ip: 515 fip = cloud.network.find_ip(public_vip_address) 516 if not fip: 517 module.fail_json( 518 msg='Public IP %s is unavailable' % public_vip_address 519 ) 520 521 # Release origin public ip first 522 cloud.network.update_ip( 523 orig_public_ip, 524 fixed_ip_address=None, 525 port_id=None 526 ) 527 528 # Associate new public ip 529 cloud.network.update_ip( 530 fip, 531 fixed_ip_address=lb.vip_address, 532 port_id=lb.vip_port_id 533 ) 534 535 new_public_ip = public_vip_address 536 changed = True 537 elif allocate_fip and not orig_public_ip: 538 fip = cloud.network.find_available_ip() 539 if not fip: 540 if not public_network: 541 module.fail_json(msg="Public network is not provided") 542 543 pub_net = cloud.network.find_network(public_network) 544 if not pub_net: 545 module.fail_json( 546 msg='Public network %s not found' % 547 public_network 548 ) 549 fip = cloud.network.create_ip( 550 floating_network_id=pub_net.id 551 ) 552 553 cloud.network.update_ip( 554 fip, 555 fixed_ip_address=lb.vip_address, 556 port_id=lb.vip_port_id 557 ) 558 559 new_public_ip = fip.floating_ip_address 560 changed = True 561 562 # Include public_vip_address in the result. 563 lb = cloud.load_balancer.find_load_balancer(name_or_id=lb.id) 564 lb_dict = lb.to_dict() 565 lb_dict.update({"public_vip_address": new_public_ip}) 566 567 module.exit_json( 568 changed=changed, 569 loadbalancer=lb_dict, 570 id=lb.id 571 ) 572 elif module.params['state'] == 'absent': 573 changed = False 574 public_vip_address = None 575 576 if lb: 577 if delete_fip: 578 ips = cloud.network.ips( 579 port_id=lb.vip_port_id, 580 fixed_ip_address=lb.vip_address 581 ) 582 ips = list(ips) 583 if ips: 584 public_vip_address = ips[0] 585 586 # Deleting load balancer with `cascade=False` does not make 587 # sense because the deletion will always fail if there are 588 # sub-resources. 589 cloud.load_balancer.delete_load_balancer(lb, cascade=True) 590 changed = True 591 592 if module.params['wait']: 593 _wait_for_lb(module, cloud, lb, "DELETED", ["ERROR"]) 594 595 if delete_fip and public_vip_address: 596 cloud.network.delete_ip(public_vip_address) 597 changed = True 598 599 module.exit_json(changed=changed) 600 except sdk.exceptions.OpenStackCloudException as e: 601 module.fail_json(msg=str(e), extra_data=e.extra_data) 602 603 604if __name__ == "__main__": 605 main() 606