1""" 2Copyright 2015 Rackspace 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15""" 16import logging 17import os 18 19from tempest.lib.cli import base 20 21from designateclient.functionaltests.config import cfg 22from designateclient.functionaltests.models import FieldValueModel 23from designateclient.functionaltests.models import ListModel 24 25 26LOG = logging.getLogger(__name__) 27 28 29def build_option_string(options): 30 """Format a string of option flags (--key 'value'). 31 32 This will quote the values, in case spaces are included. 33 Any values that are None are excluded entirely. 34 35 Usage:: 36 37 build_option_string({ 38 "--email": "me@example.com", 39 "--name": "example.com." 40 "--ttl": None, 41 42 }) 43 44 Returns:: 45 46 "--email 'me@example.com' --name 'example.com.' 47 """ 48 return " ".join("{0} '{1}'".format(flag, value) 49 for flag, value in options.items() 50 if value is not None) 51 52 53def build_flags_string(flags): 54 """Format a string of value-less flags. 55 56 Pass in a dictionary mapping flags to booleans. Those flags set to true 57 are included in the returned string. 58 59 Usage:: 60 61 build_flags_string({ 62 '--no-ttl': True, 63 '--no-name': False, 64 '--verbose': True, 65 }) 66 67 Returns:: 68 69 '--no-ttl --verbose' 70 """ 71 flags = {flag: is_set for flag, is_set in flags.items() if is_set} 72 return " ".join(flags.keys()) 73 74 75class ZoneCommands(object): 76 """This is a mixin that provides zone commands to DesignateCLI""" 77 78 def zone_list(self, *args, **kwargs): 79 return self.parsed_cmd('zone list', ListModel, *args, **kwargs) 80 81 def zone_show(self, id, *args, **kwargs): 82 return self.parsed_cmd('zone show %s' % id, FieldValueModel, *args, 83 **kwargs) 84 85 def zone_delete(self, id, *args, **kwargs): 86 return self.parsed_cmd('zone delete %s' % id, FieldValueModel, *args, 87 **kwargs) 88 89 def zone_create(self, name, email=None, ttl=None, description=None, 90 type=None, masters=None, *args, **kwargs): 91 options_str = build_option_string({ 92 "--email": email, 93 "--ttl": ttl, 94 "--description": description, 95 "--masters": masters, 96 "--type": type, 97 }) 98 cmd = 'zone create {0} {1}'.format(name, options_str) 99 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 100 101 def zone_set(self, id, email=None, ttl=None, description=None, 102 type=None, masters=None, *args, **kwargs): 103 options_str = build_option_string({ 104 "--email": email, 105 "--ttl": ttl, 106 "--description": description, 107 "--masters": masters, 108 "--type": type, 109 }) 110 cmd = 'zone set {0} {1}'.format(id, options_str) 111 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 112 113 114class ZoneTransferCommands(object): 115 """A mixin for DesignateCLI to add zone transfer commands""" 116 117 def zone_transfer_request_list(self, *args, **kwargs): 118 cmd = 'zone transfer request list' 119 return self.parsed_cmd(cmd, ListModel, *args, **kwargs) 120 121 def zone_transfer_request_create(self, zone_id, target_project_id=None, 122 description=None, *args, **kwargs): 123 options_str = build_option_string({ 124 "--target-project-id": target_project_id, 125 "--description": description, 126 }) 127 cmd = 'zone transfer request create {0} {1}'.format( 128 zone_id, options_str) 129 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 130 131 def zone_transfer_request_show(self, id, *args, **kwargs): 132 cmd = 'zone transfer request show {0}'.format(id) 133 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 134 135 def zone_transfer_request_set(self, id, description=None, *args, **kwargs): 136 options_str = build_option_string({"--description": description}) 137 cmd = 'zone transfer request set {0} {1}'.format(options_str, id) 138 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 139 140 def zone_transfer_request_delete(self, id, *args, **kwargs): 141 cmd = 'zone transfer request delete {0}'.format(id) 142 return self.parsed_cmd(cmd, *args, **kwargs) 143 144 def zone_transfer_accept_request(self, id, key, *args, **kwargs): 145 options_str = build_option_string({ 146 "--transfer-id": id, 147 "--key": key, 148 }) 149 cmd = 'zone transfer accept request {0}'.format(options_str) 150 return self.parsed_cmd(cmd, *args, **kwargs) 151 152 def zone_transfer_accept_show(self, id, *args, **kwargs): 153 cmd = 'zone transfer accept show {0}'.format(id) 154 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 155 156 def zone_transfer_accept_list(self, *args, **kwargs): 157 cmd = 'zone transfer accept list' 158 return self.parsed_cmd(cmd, ListModel, *args, **kwargs) 159 160 161class ZoneExportCommands(object): 162 """A mixin for DesignateCLI to add zone export commands""" 163 164 def zone_export_list(self, *args, **kwargs): 165 cmd = 'zone export list' 166 return self.parsed_cmd(cmd, ListModel, *args, **kwargs) 167 168 def zone_export_create(self, zone_id, *args, **kwargs): 169 cmd = 'zone export create {0}'.format( 170 zone_id) 171 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 172 173 def zone_export_show(self, zone_export_id, *args, **kwargs): 174 cmd = 'zone export show {0}'.format(zone_export_id) 175 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 176 177 def zone_export_delete(self, zone_export_id, *args, **kwargs): 178 cmd = 'zone export delete {0}'.format(zone_export_id) 179 return self.parsed_cmd(cmd, *args, **kwargs) 180 181 def zone_export_showfile(self, zone_export_id, *args, **kwargs): 182 cmd = 'zone export showfile {0}'.format(zone_export_id) 183 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 184 185 186class ZoneImportCommands(object): 187 """A mixin for DesignateCLI to add zone import commands""" 188 189 def zone_import_list(self, *args, **kwargs): 190 cmd = 'zone import list' 191 return self.parsed_cmd(cmd, ListModel, *args, **kwargs) 192 193 def zone_import_create(self, zone_file_path, *args, **kwargs): 194 cmd = 'zone import create {0}'.format(zone_file_path) 195 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 196 197 def zone_import_show(self, zone_import_id, *args, **kwargs): 198 cmd = 'zone import show {0}'.format(zone_import_id) 199 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 200 201 def zone_import_delete(self, zone_import_id, *args, **kwargs): 202 cmd = 'zone import delete {0}'.format(zone_import_id) 203 return self.parsed_cmd(cmd, *args, **kwargs) 204 205 206class RecordsetCommands(object): 207 208 def recordset_show(self, zone_id, id, *args, **kwargs): 209 cmd = 'recordset show {0} {1}'.format(zone_id, id) 210 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 211 212 def recordset_list(self, zone_id, *args, **kwargs): 213 cmd = 'recordset list {0}'.format(zone_id) 214 return self.parsed_cmd(cmd, ListModel, *args, **kwargs) 215 216 def recordset_create(self, zone_id, name, records=None, type=None, 217 description=None, ttl=None, *args, **kwargs): 218 options_str = build_option_string({ 219 '--records': records, 220 '--type': type, 221 '--description': description, 222 '--ttl': ttl, 223 }) 224 cmd = 'recordset create {0} {1} {2}'.format(zone_id, name, options_str) 225 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 226 227 def recordset_set(self, zone_id, id, records=None, type=None, 228 description=None, ttl=None, no_description=False, 229 no_ttl=False, *args, **kwargs): 230 options_str = build_option_string({ 231 '--records': records, 232 '--type': type, 233 '--description': description, 234 '--ttl': ttl, 235 }) 236 flags_str = build_flags_string({ 237 '--no-description': no_description, 238 '--no-ttl': no_ttl, 239 }) 240 cmd = 'recordset set {0} {1} {2} {3}'.format( 241 zone_id, id, flags_str, options_str) 242 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 243 244 def recordset_delete(self, zone_id, id, *args, **kwargs): 245 cmd = 'recordset delete {0} {1}'.format(zone_id, id) 246 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 247 248 249class TLDCommands(object): 250 251 def tld_list(self, *args, **kwargs): 252 return self.parsed_cmd('tld list', ListModel, *args, **kwargs) 253 254 def tld_show(self, id, *args, **kwargs): 255 return self.parsed_cmd('tld show {0}'.format(id), FieldValueModel, 256 *args, **kwargs) 257 258 def tld_delete(self, id, *args, **kwargs): 259 return self.parsed_cmd('tld delete {0}'.format(id), *args, **kwargs) 260 261 def tld_create(self, name, description=None, *args, **kwargs): 262 options_str = build_option_string({ 263 '--name': name, 264 '--description': description, 265 }) 266 cmd = 'tld create {0}'.format(options_str) 267 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 268 269 def tld_set(self, id, name=None, description=None, no_description=False, 270 *args, **kwargs): 271 options_str = build_option_string({ 272 '--name': name, 273 '--description': description, 274 }) 275 flags_str = build_flags_string({'--no-description': no_description}) 276 cmd = 'tld set {0} {1} {2}'.format(id, options_str, flags_str) 277 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 278 279 280class TSIGKeyCommands(object): 281 def tsigkey_list(self, *args, **kwargs): 282 return self.parsed_cmd('tsigkey list', ListModel, *args, **kwargs) 283 284 def tsigkey_show(self, id, *args, **kwargs): 285 return self.parsed_cmd('tsigkey show {0}'.format(id), FieldValueModel, 286 *args, **kwargs) 287 288 def tsigkey_delete(self, id, *args, **kwargs): 289 return self.parsed_cmd('tsigkey delete {0}'.format(id), *args, 290 **kwargs) 291 292 def tsigkey_create(self, name, algorithm, secret, scope, resource_id, 293 *args, **kwargs): 294 options_str = build_option_string({ 295 '--name': name, 296 '--algorithm': algorithm, 297 '--secret': secret, 298 '--scope': scope, 299 '--resource-id': resource_id, 300 }) 301 cmd = 'tsigkey create {0}'.format(options_str) 302 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 303 304 def tsigkey_set(self, id, name=None, algorithm=None, secret=None, 305 scope=None, 306 *args, **kwargs): 307 options_str = build_option_string({ 308 '--name': name, 309 '--algorithm': algorithm, 310 '--secret': secret, 311 '--scope': scope, 312 }) 313 cmd = 'tsigkey set {0} {1}'.format(id, options_str) 314 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 315 316 317class BlacklistCommands(object): 318 def zone_blacklist_list(self, *args, **kwargs): 319 cmd = 'zone blacklist list' 320 return self.parsed_cmd(cmd, ListModel, *args, **kwargs) 321 322 def zone_blacklist_create(self, pattern, description=None, *args, 323 **kwargs): 324 options_str = build_option_string({ 325 '--pattern': pattern, 326 '--description': description, 327 }) 328 cmd = 'zone blacklist create {0}'.format(options_str) 329 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 330 331 def zone_blacklist_set(self, id, pattern=None, description=None, 332 no_description=False, *args, **kwargs): 333 options_str = build_option_string({ 334 '--pattern': pattern, 335 '--description': description, 336 }) 337 flags_str = build_flags_string({'--no-description': no_description}) 338 cmd = 'zone blacklist set {0} {1} {2}'.format(id, options_str, 339 flags_str) 340 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 341 342 def zone_blacklist_show(self, id, *args, **kwargs): 343 cmd = 'zone blacklist show {0}'.format(id) 344 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 345 346 def zone_blacklist_delete(self, id, *args, **kwargs): 347 cmd = 'zone blacklist delete {0}'.format(id) 348 return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs) 349 350 351class DesignateCLI(base.CLIClient, ZoneCommands, ZoneTransferCommands, 352 ZoneExportCommands, ZoneImportCommands, RecordsetCommands, 353 TLDCommands, BlacklistCommands): 354 355 # instantiate this once to minimize requests to keystone 356 _CLIENTS = None 357 358 def __init__(self, *args, **kwargs): 359 super(DesignateCLI, self).__init__(*args, **kwargs) 360 # grab the project id. this is used for zone transfer requests 361 resp = FieldValueModel(self.openstack('token issue')) 362 self.project_id = resp.project_id 363 364 @property 365 def using_auth_override(self): 366 return bool(cfg.CONF.identity.override_endpoint) 367 368 @classmethod 369 def get_clients(cls): 370 if not cls._CLIENTS: 371 cls._init_clients() 372 return cls._CLIENTS 373 374 @classmethod 375 def _init_clients(cls): 376 cls._CLIENTS = { 377 'default': DesignateCLI( 378 cli_dir=cfg.CONF.designateclient.directory, 379 username=cfg.CONF.identity.username, 380 password=cfg.CONF.identity.password, 381 tenant_name=cfg.CONF.identity.tenant_name, 382 uri=cfg.CONF.identity.uri, 383 ), 384 'alt': DesignateCLI( 385 cli_dir=cfg.CONF.designateclient.directory, 386 username=cfg.CONF.identity.alt_username, 387 password=cfg.CONF.identity.alt_password, 388 tenant_name=cfg.CONF.identity.alt_tenant_name, 389 uri=cfg.CONF.identity.uri, 390 ), 391 'admin': DesignateCLI( 392 cli_dir=cfg.CONF.designateclient.directory, 393 username=cfg.CONF.identity.admin_username, 394 password=cfg.CONF.identity.admin_password, 395 tenant_name=cfg.CONF.identity.admin_tenant_name, 396 uri=cfg.CONF.identity.uri, 397 ) 398 } 399 400 @classmethod 401 def as_user(self, user): 402 clients = self.get_clients() 403 if user in clients: 404 return clients[user] 405 raise Exception("User '{0}' does not exist".format(user)) 406 407 def parsed_cmd(self, cmd, model=None, *args, **kwargs): 408 if self.using_auth_override: 409 # use --os-url and --os-token 410 func = self._openstack_noauth 411 else: 412 # use --os-username --os-tenant-name --os-password --os-auth-url 413 func = self.openstack 414 415 out = func(cmd, *args, **kwargs) 416 LOG.debug(out) 417 if model is not None: 418 return model(out) 419 return out 420 421 def _openstack_noauth(self, cmd, *args, **kwargs): 422 exe = os.path.join(cfg.CONF.designateclient.directory, 'openstack') 423 options = build_option_string({ 424 '--os-url': cfg.CONF.identity.override_endpoint, 425 '--os-token': cfg.CONF.identity.override_token, 426 }) 427 cmd = options + " " + cmd 428 return base.execute(exe, cmd, *args, **kwargs) 429