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

# Copyright 2010 Jacob Kaplan-Moss 

# Copyright 2011 OpenStack LLC. 

# All Rights Reserved. 

# 

#    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. 

 

""" 

Base utilities to build API operation managers and objects on top of. 

""" 

 

import abc 

import urllib 

 

from keystoneclient import exceptions 

 

 

# Python 2.4 compat 

try: 

    all 

except NameError: 

    def all(iterable): 

        return True not in (not x for x in iterable) 

 

 

def getid(obj): 

    """ 

    Abstracts the common pattern of allowing both an object or an object's ID 

    (UUID) as a parameter when dealing with relationships. 

    """ 

 

    # Try to return the object's UUID first, if we have a UUID. 

    try: 

44   47        if obj.uuid: 

            return obj.uuid 

    except AttributeError: 

        pass 

    try: 

        return obj.id 

    except AttributeError: 

        return obj 

 

 

class Manager(object): 

    """ 

    Managers interact with a particular type of API (servers, flavors, images, 

    etc.) and provide CRUD operations for them. 

    """ 

    resource_class = None 

 

    def __init__(self, api): 

        self.api = api 

 

    def _list(self, url, response_key, obj_class=None, body=None): 

        resp = None 

66        if body: 

            resp, body = self.api.post(url, body=body) 

        else: 

            resp, body = self.api.get(url) 

 

73        if obj_class is None: 

            obj_class = self.resource_class 

 

        data = body[response_key] 

        # NOTE(ja): keystone returns values as list as {'values': [ ... ]} 

        #           unlike other services which just return the list... 

        if type(data) is dict: 

            data = data['values'] 

        return [obj_class(self, res, loaded=True) for res in data if res] 

 

    def _get(self, url, response_key): 

        resp, body = self.api.get(url) 

        return self.resource_class(self, body[response_key], loaded=True) 

 

    def _head(self, url): 

        resp, body = self.api.head(url) 

        return resp.status_code == 204 

 

    def _create(self, url, body, response_key, return_raw=False): 

        resp, body = self.api.post(url, body=body) 

91        if return_raw: 

            return body[response_key] 

        return self.resource_class(self, body[response_key]) 

 

    def _delete(self, url): 

        resp, body = self.api.delete(url) 

 

    def _update(self, url, body=None, response_key=None, method="PUT", 

                management=True): 

        methods = {"PUT": self.api.put, 

                   "POST": self.api.post, 

                   "PATCH": self.api.patch} 

        try: 

            if body is not None: 

                resp, body = methods[method](url, body=body, 

                                             management=management) 

            else: 

                resp, body = methods[method](url, management=management) 

        except KeyError: 

            raise exceptions.ClientException("Invalid update method: %s" 

                                             % method) 

        # PUT requests may not return a body 

        if body: 

            return self.resource_class(self, body[response_key]) 

 

 

class ManagerWithFind(Manager): 

    """ 

    Like a `Manager`, but with additional `find()`/`findall()` methods. 

    """ 

 

    __metaclass__ = abc.ABCMeta 

 

    @abc.abstractmethod 

    def list(self): 

        pass 

 

    def find(self, **kwargs): 

        """ 

        Find a single item with attributes matching ``**kwargs``. 

 

        This isn't very efficient: it loads the entire list then filters on 

        the Python side. 

        """ 

        rl = self.findall(**kwargs) 

        num = len(rl) 

 

        if num == 0: 

            msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) 

            raise exceptions.NotFound(404, msg) 

        elif num > 1: 

            raise exceptions.NoUniqueMatch 

        else: 

            return rl[0] 

 

    def findall(self, **kwargs): 

        """ 

        Find all items with attributes matching ``**kwargs``. 

 

        This isn't very efficient: it loads the entire list then filters on 

        the Python side. 

        """ 

        found = [] 

        searches = kwargs.items() 

 

        for obj in self.list(): 

            try: 

                if all(getattr(obj, attr) == value 

                       for (attr, value) in searches): 

                    found.append(obj) 

            except AttributeError: 

                continue 

 

        return found 

 

 

class CrudManager(Manager): 

    """Base manager class for manipulating Keystone entities. 

 

    Children of this class are expected to define a `collection_key` and `key`. 

 

    - `collection_key`: Usually a plural noun by convention (e.g. `entities`); 

      used to refer collections in both URL's (e.g.  `/v3/entities`) and JSON 

      objects containing a list of member resources (e.g. `{'entities': [{}, 

      {}, {}]}`). 

    - `key`: Usually a singular noun by convention (e.g. `entity`); used to 

      refer to an individual member of the collection. 

 

    """ 

    collection_key = None 

    key = None 

 

    def build_url(self, base_url=None, **kwargs): 

        """Builds a resource URL for the given kwargs. 

 

        Given an example collection where `collection_key = 'entities'` and 

        `key = 'entity'`, the following URL's could be generated. 

 

        By default, the URL will represent a collection of entities, e.g.:: 

 

            /entities 

 

        If kwargs contains an `entity_id`, then the URL will represent a 

        specific member, e.g.:: 

 

            /entities/{entity_id} 

 

        If a `base_url` is provided, the generated URL will be appended to it. 

 

        """ 

        url = base_url if base_url is not None else '' 

 

        url += '/%s' % self.collection_key 

 

        # do we have a specific entity? 

        entity_id = kwargs.get('%s_id' % self.key) 

        if entity_id is not None: 

            url += '/%s' % entity_id 

 

        return url 

 

    def _filter_kwargs(self, kwargs): 

        # drop null values 

        for key, ref in kwargs.copy().iteritems(): 

            if ref is None: 

                kwargs.pop(key) 

            else: 

                id_value = getid(ref) 

219                if id_value != ref: 

                    kwargs.pop(key) 

                    kwargs['%s_id' % key] = id_value 

        return kwargs 

 

    def create(self, **kwargs): 

        kwargs = self._filter_kwargs(kwargs) 

        return self._create( 

            self.build_url(**kwargs), 

            {self.key: kwargs}, 

            self.key) 

 

    def get(self, **kwargs): 

        kwargs = self._filter_kwargs(kwargs) 

        return self._get( 

            self.build_url(**kwargs), 

            self.key) 

 

    def head(self, **kwargs): 

        kwargs = self._filter_kwargs(kwargs) 

        return self._head(self.build_url(**kwargs)) 

 

    def list(self, base_url=None, **kwargs): 

        kwargs = self._filter_kwargs(kwargs) 

 

        return self._list( 

            '%(base_url)s%(query)s' % { 

                'base_url': self.build_url(base_url=base_url, **kwargs), 

                'query': '?%s' % urllib.urlencode(kwargs) if kwargs else '', 

            }, 

            self.collection_key) 

 

    def put(self, base_url=None, **kwargs): 

        kwargs = self._filter_kwargs(kwargs) 

 

        return self._update( 

            self.build_url(base_url=base_url, **kwargs), 

            method='PUT') 

 

    def update(self, **kwargs): 

        kwargs = self._filter_kwargs(kwargs) 

        params = kwargs.copy() 

        params.pop('%s_id' % self.key) 

 

        return self._update( 

            self.build_url(**kwargs), 

            {self.key: params}, 

            self.key, 

            method='PATCH') 

 

    def delete(self, **kwargs): 

        kwargs = self._filter_kwargs(kwargs) 

 

        return self._delete( 

            self.build_url(**kwargs)) 

 

 

class Resource(object): 

    """ 

    A resource represents a particular instance of an object (tenant, user, 

    etc). This is pretty much just a bag for attributes. 

 

    :param manager: Manager object 

    :param info: dictionary representing resource attributes 

    :param loaded: prevent lazy-loading if set to True 

    """ 

    def __init__(self, manager, info, loaded=False): 

        self.manager = manager 

        self._info = info 

        self._add_details(info) 

        self._loaded = loaded 

 

    def _add_details(self, info): 

        for (k, v) in info.iteritems(): 

            setattr(self, k, v) 

 

    def __getattr__(self, k): 

303        if k not in self.__dict__: 

            #NOTE(bcwaldon): disallow lazy-loading if already loaded once 

            if not self.is_loaded(): 

                self.get() 

                return self.__getattr__(k) 

 

            raise AttributeError(k) 

        else: 

            return self.__dict__[k] 

 

    def __repr__(self): 

        reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and 

                          k != 'manager') 

        info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) 

        return "<%s %s>" % (self.__class__.__name__, info) 

 

    def get(self): 

        # set_loaded() first ... so if we have to bail, we know we tried. 

        self.set_loaded(True) 

        if not hasattr(self.manager, 'get'): 

            return 

 

        new = self.manager.get(self.id) 

exit        if new: 

            self._add_details(new._info) 

 

    def delete(self): 

        return self.manager.delete(self) 

 

    def __eq__(self, other): 

326        if not isinstance(other, self.__class__): 

            return False 

        if hasattr(self, 'id') and hasattr(other, 'id'): 

            return self.id == other.id 

        return self._info == other._info 

 

    def is_loaded(self): 

        return self._loaded 

 

    def set_loaded(self, val): 

        self._loaded = val