Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

# Copyright 2013 - Red Hat, Inc. 

# 

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

# you may not use this file except in compliance with the License. 

# You may obtain a copy of the License at 

# 

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

# 

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

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

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

# See the License for the specific language governing permissions and 

# limitations under the License. 

 

"""Magnum base exception handling. 

 

Includes decorator for re-raising Magnum-type exceptions. 

 

""" 

 

import functools 

import sys 

import uuid 

 

from keystoneclient import exceptions as keystone_exceptions 

from oslo_config import cfg 

from oslo_utils import excutils 

import pecan 

import six 

import wsme 

 

from magnum.common import safe_utils 

from magnum.openstack.common._i18n import _ 

from magnum.openstack.common._i18n import _LE 

from magnum.openstack.common import log as logging 

 

 

LOG = logging.getLogger(__name__) 

 

CONF = cfg.CONF 

 

try: 

    CONF.import_opt('fatal_exception_format_errors', 

                'oslo_versionedobjects.exception') 

except cfg.NoSuchOptError as e: 

    # Note:work around for magnum run against master branch 

    # in devstack gate job, as magnum not branched yet 

    # verisonobjects kilo/master different version can 

    # cause issue here. As it changed import group. So 

    # add here before branch to prevent gate failure. 

    # Bug: #1447873 

    CONF.import_opt('fatal_exception_format_errors', 

                    'oslo_versionedobjects.exception', 

                    group='oslo_versionedobjects') 

 

 

def wrap_exception(notifier=None, publisher_id=None, event_type=None, 

                   level=None): 

    """This decorator wraps a method to catch any exceptions. 

 

    It logs the exception as well as optionally sending 

    it to the notification system. 

    """ 

    def inner(f): 

        def wrapped(self, context, *args, **kw): 

            # Don't store self or context in the payload, it now seems to 

            # contain confidential information. 

            try: 

                return f(self, context, *args, **kw) 

            except Exception as e: 

                with excutils.save_and_reraise_exception(): 

                    if notifier: 

                        call_dict = safe_utils.getcallargs(f, *args, **kw) 

                        payload = dict(exception=e, 

                                       private=dict(args=call_dict) 

                                       ) 

 

                        # Use a temp vars so we don't shadow 

                        # our outer definitions. 

                        temp_level = level 

                        if not temp_level: 

                            temp_level = notifier.ERROR 

 

                        temp_type = event_type 

                        if not temp_type: 

                            # If f has multiple decorators, they must use 

                            # functools.wraps to ensure the name is 

                            # propagated. 

                            temp_type = f.__name__ 

 

                        notifier.notify(context, publisher_id, temp_type, 

                                        temp_level, payload) 

 

        return functools.wraps(f)(wrapped) 

    return inner 

 

 

OBFUSCATED_MSG = _('Your request could not be handled ' 

                   'because of a problem in the server. ' 

                   'Error Correlation id is: %s') 

 

 

def wrap_controller_exception(func, func_server_error, func_client_error): 

    """This decorator wraps controllers methods to handle exceptions: 

 

    - if an unhandled Exception or a MagnumException with an error code >=500 

    is catched, raise a http 5xx ClientSideError and correlates it with a log 

    message 

 

    - if a MagnumException is catched and its error code is <500, raise a http 

    4xx and logs the excp in debug mode 

 

    """ 

    @functools.wraps(func) 

    def wrapped(*args, **kw): 

        try: 

            return func(*args, **kw) 

        except Exception as excp: 

            if isinstance(excp, MagnumException): 

                http_error_code = excp.code 

            else: 

                http_error_code = 500 

 

            if http_error_code >= 500: 

                # log the error message with its associated 

                # correlation id 

                log_correlation_id = str(uuid.uuid4()) 

                LOG.error(_LE("%(correlation_id)s:%(excp)s") % 

                             {'correlation_id': log_correlation_id, 

                              'excp': str(excp)}) 

                # raise a client error with an obfuscated message 

                func_server_error(log_correlation_id, http_error_code) 

            else: 

                # raise a client error the original message 

                LOG.debug(excp) 

                func_client_error(excp, http_error_code) 

    return wrapped 

 

 

def wrap_wsme_controller_exception(func): 

    """This decorator wraps wsme controllers to handle exceptions.""" 

    def _func_server_error(log_correlation_id, status_code): 

        raise wsme.exc.ClientSideError( 

            six.text_type(OBFUSCATED_MSG % log_correlation_id), status_code) 

 

    def _func_client_error(excp, status_code): 

        raise wsme.exc.ClientSideError(six.text_type(excp), status_code) 

 

    return wrap_controller_exception(func, 

                                     _func_server_error, 

                                     _func_client_error) 

 

 

def wrap_pecan_controller_exception(func): 

    """This decorator wraps pecan controllers to handle exceptions.""" 

    def _func_server_error(log_correlation_id, status_code): 

        pecan.response.status = status_code 

        pecan.response.text = six.text_type(OBFUSCATED_MSG % 

                                            log_correlation_id) 

 

    def _func_client_error(excp, status_code): 

        pecan.response.status = status_code 

        pecan.response.text = six.text_type(excp) 

        pecan.response.content_type = None 

 

    return wrap_controller_exception(func, 

                                     _func_server_error, 

                                     _func_client_error) 

 

 

def wrap_keystone_exception(func): 

    """Wrap keystone exceptions and throw Magnum specific exceptions.""" 

    @functools.wraps(func) 

    def wrapped(*args, **kw): 

        try: 

            return func(*args, **kw) 

        except keystone_exceptions.AuthorizationFailure: 

            raise AuthorizationFailure( 

                client=func.__name__, message="reason: %s" % sys.exc_info()[1]) 

        except keystone_exceptions.ClientException: 

            raise AuthorizationFailure( 

                client=func.__name__, 

                message="unexpected keystone client error occurred: %s" 

                        % sys.exc_info()[1]) 

    return wrapped 

 

 

class MagnumException(Exception): 

    """Base Magnum Exception 

 

    To correctly use this class, inherit from it and define 

    a 'message' property. That message will get printf'd 

    with the keyword arguments provided to the constructor. 

 

    """ 

    message = _("An unknown exception occurred.") 

    code = 500 

 

    def __init__(self, message=None, **kwargs): 

        self.kwargs = kwargs 

 

        if 'code' not in self.kwargs: 

            try: 

                self.kwargs['code'] = self.code 

            except AttributeError: 

                pass 

 

        if message: 

            self.message = message 

 

        try: 

            self.message = self.message % kwargs 

        except Exception as e: 

            # kwargs doesn't match a variable in the message 

            # log the issue and the kwargs 

            LOG.exception(_LE('Exception in string format operation')) 

            for name, value in kwargs.iteritems(): 

                LOG.error(_LE("%(name)s: %(value)s") % 

                             {'name': name, 'value': value}) 

            try: 

222   228                if CONF.fatal_exception_format_errors: 

                    raise e 

            except cfg.NoSuchOptError: 

                # Note: work around for Bug: #1447873 

226                if CONF.oslo_versionedobjects.fatal_exception_format_errors: 

                    raise e 

 

        super(MagnumException, self).__init__(self.message) 

 

    def __str__(self): 

232        if six.PY3: 

            return self.message 

        return self.message.encode('utf-8') 

 

    def __unicode__(self): 

        return self.message 

 

    def format_message(self): 

        if self.__class__.__name__.endswith('_Remote'): 

            return self.args[0] 

        else: 

            return six.text_type(self) 

 

 

class ObjectNotFound(MagnumException): 

    message = _("The %(name)s %(id)s could not be found.") 

 

 

class ObjectNotUnique(MagnumException): 

    message = _("The %(name)s already exists.") 

 

 

class ResourceNotFound(ObjectNotFound): 

    message = _("The %(name)s resource %(id)s could not be found.") 

    code = 404 

 

 

class ResourceExists(ObjectNotUnique): 

    message = _("The %(name)s resource already exists.") 

    code = 409 

 

 

class AuthorizationFailure(MagnumException): 

    message = _("%(client)s connection failed. %(message)s") 

 

 

class UnsupportedObjectError(MagnumException): 

    message = _('Unsupported object type %(objtype)s') 

 

 

class IncompatibleObjectVersion(MagnumException): 

    message = _('Version %(objver)s of %(objname)s is not supported') 

 

 

class OrphanedObjectError(MagnumException): 

    message = _('Cannot call %(method)s on orphaned %(objtype)s object') 

 

 

class Invalid(MagnumException): 

    message = _("Unacceptable parameters.") 

    code = 400 

 

 

class InvalidUUID(Invalid): 

    message = _("Expected a uuid but received %(uuid)s.") 

 

 

class InvalidName(Invalid): 

    message = _("Expected a name but received %(uuid)s.") 

 

 

class InvalidUuidOrName(Invalid): 

    message = _("Expected a name or uuid but received %(uuid)s.") 

 

 

class InvalidIdentity(Invalid): 

    message = _("Expected an uuid or int but received %(identity)s.") 

 

 

class HTTPNotFound(ResourceNotFound): 

    pass 

 

 

class Conflict(MagnumException): 

    message = _('Conflict.') 

    code = 409 

 

 

class InvalidState(Conflict): 

    message = _("Invalid resource state.") 

 

 

# Cannot be templated as the error syntax varies. 

# msg needs to be constructed when raised. 

class InvalidParameterValue(Invalid): 

    message = _("%(err)s") 

 

 

class InstanceAssociated(Conflict): 

    message = _("Instance %(instance_uuid)s is already associated with a node," 

                " it cannot be associated with this other node %(node)s") 

 

 

class InstanceNotFound(ResourceNotFound): 

    message = _("Instance %(instance)s could not be found.") 

 

 

class PatchError(Invalid): 

    message = _("Couldn't apply patch '%(patch)s'. Reason: %(reason)s") 

 

 

class NotAuthorized(MagnumException): 

    message = _("Not authorized.") 

    code = 403 

 

 

class OperationNotPermitted(NotAuthorized): 

    message = _("Operation not permitted.") 

 

 

class InvalidMAC(Invalid): 

    message = _("Expected a MAC address but received %(mac)s.") 

 

 

class ConfigInvalid(MagnumException): 

    message = _("Invalid configuration file. %(error_msg)s") 

 

 

class NodeAlreadyExists(Conflict): 

    message = _("A node with UUID %(uuid)s already exists.") 

 

 

class NodeNotFound(ResourceNotFound): 

    message = _("Node %(node)s could not be found.") 

 

 

class NodeAssociated(InvalidState): 

    message = _("Node %(node)s is associated with instance %(instance)s.") 

 

 

class SSHConnectFailed(MagnumException): 

    message = _("Failed to establish SSH connection to host %(host)s.") 

 

 

class FileSystemNotSupported(MagnumException): 

    message = _("Failed to create a file system. " 

                "File system %(fs)s is not supported.") 

 

 

class BayModelNotFound(ResourceNotFound): 

    message = _("Baymodel %(baymodel)s could not be found.") 

 

 

class BayModelAlreadyExists(Conflict): 

    message = _("A baymodel with UUID %(uuid)s already exists.") 

 

 

class BayModelReferenced(Invalid): 

    message = _("Baymodel %(baymodel)s is referenced by one or multiple bays.") 

 

 

class BayNotFound(ResourceNotFound): 

    message = _("Bay %(bay)s could not be found.") 

 

 

class BayAlreadyExists(Conflict): 

    message = _("A node with UUID %(uuid)s already exists.") 

 

 

class ContainerNotFound(ResourceNotFound): 

    message = _("Container %(container)s could not be found.") 

 

 

class ContainerAlreadyExists(Conflict): 

    message = _("A container with UUID %(uuid)s already exists.") 

 

 

class PodNotFound(ResourceNotFound): 

    message = _("Pod %(pod)s could not be found.") 

 

 

class PodAlreadyExists(Conflict): 

    message = _("A node with UUID %(uuid)s already exists.") 

 

 

class ReplicationControllerNotFound(ResourceNotFound): 

    message = _("ReplicationController %(rc)s could not be found.") 

 

 

class ReplicationControllerAlreadyExists(Conflict): 

    message = _("A ReplicationController with UUID %(uuid)s already exists.") 

 

 

class ServiceNotFound(ResourceNotFound): 

    message = _("Service %(service)s could not be found.") 

 

 

class ServiceAlreadyExists(Conflict): 

    message = _("A node with UUID %(uuid)s already exists.") 

 

 

class ContainerException(Exception): 

    pass 

 

 

class NotSupported(MagnumException): 

    message = _("%(operation)s is not supported.") 

    code = 400 

 

 

class BayTypeNotSupported(MagnumException): 

    message = _("Bay type (%(platform)s, %(os)s, %(coe)s) not supported.") 

 

 

class BayTypeNotEnabled(MagnumException): 

    message = _("Bay type (%(platform)s, %(os)s, %(coe)s) not enabled.") 

 

 

class RequiredParameterNotProvided(MagnumException): 

    message = _("Required parameter %(heat_param)s not provided.") 

 

 

class Urllib2InvalidScheme(MagnumException): 

    message = _("The urllib2 URL %(url) has an invalid scheme.") 

 

 

class OperationInProgress(Invalid): 

    message = _("Bay %(bay_name)s already has an operation in progress.") 

 

 

class ImageNotFound(ResourceNotFound): 

    message = _("Image %(image_id)s could not be found.") 

 

 

class ImageNotAuthorized(MagnumException): 

    message = _("Not authorized for image %(image_id)s.") 

 

 

class OSDistroFieldNotFound(ResourceNotFound): 

    message = _("Image %(image_id)s doesn't contain os-distro field.") 

 

 

class KubernetesAPIFailed(MagnumException): 

    def __init__(self, message=None, **kwargs): 

        self.__class__.code = kwargs.get('code') 

        super(KubernetesAPIFailed, self).__init__(message, **kwargs)