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

""" 

Copyright (c) 2012-2013 RockStor, Inc. <http://rockstor.com> 

This file is part of RockStor. 

 

RockStor is free software; you can redistribute it and/or modify 

it under the terms of the GNU General Public License as published 

by the Free Software Foundation; either version 2 of the License, 

or (at your option) any later version. 

 

RockStor is distributed in the hope that it will be useful, but 

WITHOUT ANY WARRANTY; without even the implied warranty of 

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 

General Public License for more details. 

 

You should have received a copy of the GNU General Public License 

along with this program. If not, see <http://www.gnu.org/licenses/>. 

""" 

import re 

from datetime import datetime 

from django.utils.timezone import utc 

from django.conf import settings 

from storageadmin.models import (Share, Snapshot, SFTP) 

from smart_manager.models import ShareUsage 

from fs.btrfs import (mount_share, mount_snap, is_mounted, 

                      umount_root, shares_info, volume_usage, snaps_info, 

                      qgroup_create, update_quota, share_pqgroup_assign, 

                      qgroup_assign) 

from storageadmin.util import handle_exception 

from copy import deepcopy 

 

import logging 

 

logger = logging.getLogger(__name__) 

 

NEW_ENTRY = True 

UPDATE_TS = False 

# The following model/db default setting is also used when quotas are disabled 

# or when a Read-only state prevents creation of a new pqgroup. 

PQGROUP_DEFAULT = settings.MODEL_DEFS['pqgroup'] 

 

 

def helper_mount_share(share, mnt_pt=None): 

    if not share.is_mounted: 

        if mnt_pt is None: 

            mnt_pt = ('%s%s' % (settings.MNT_PT, share.name)) 

        mount_share(share, mnt_pt) 

 

 

def validate_share(sname, request): 

    try: 

        return Share.objects.get(name=sname) 

    except: 

        e_msg = ('Share with name: %s does not exist' % sname) 

        handle_exception(Exception(e_msg), request) 

 

 

def sftp_snap_toggle(share, mount=True): 

    for snap in Snapshot.objects.filter(share=share, uvisible=True): 

        mnt_pt = ('%s/%s/%s/.%s' % (settings.SFTP_MNT_ROOT, 

                                    share.owner, share.name, 

                                    snap.name)) 

        if (mount and not is_mounted(mnt_pt)): 

            mount_snap(share, snap.name, mnt_pt) 

        elif (is_mounted(mnt_pt) and not mount): 

            umount_root(mnt_pt) 

 

 

def toggle_sftp_visibility(share, snap_name, on=True): 

    if (not SFTP.objects.filter(share=share).exists()): 

        return 

 

    mnt_pt = ('%s/%s/%s/.%s' % (settings.SFTP_MNT_ROOT, share.owner, 

                                share.name, snap_name)) 

    if (on): 

        if (not is_mounted(mnt_pt)): 

            mount_snap(share, snap_name, mnt_pt) 

    else: 

        umount_root(mnt_pt) 

 

 

def import_shares(pool, request): 

    # Establish known shares/subvols within our db for the given pool: 

    shares_in_pool_db = [s.name for s in Share.objects.filter(pool=pool)] 

    # Find the actual/current shares/subvols within the given pool: 

    # Limited to Rockstor relevant subvols ie shares and clones. 

    shares_in_pool = shares_info(pool) 

    # List of pool's share.pqgroups so we can remove inadvertent duplication. 

    # All pqgroups are removed when quotas are disabled, combined with a part 

    # refresh we could have duplicates within the db. 

    share_pqgroups_used = [] 

    # Delete db Share object if it is no longer found on disk. 

    for s_in_pool_db in shares_in_pool_db: 

        if s_in_pool_db not in shares_in_pool: 

            logger.debug('Removing, missing on disk, share db entry ({}) from ' 

                         'pool ({}).'.format(s_in_pool_db, pool.name)) 

            Share.objects.get(pool=pool, name=s_in_pool_db).delete() 

    # Check if each share in pool also has a db counterpart. 

    for s_in_pool in shares_in_pool: 

        logger.debug('---- Share name = {}.'.format(s_in_pool)) 

        if s_in_pool in shares_in_pool_db: 

            logger.debug('Updating pre-existing same pool db share entry.') 

            # We have a pool db share counterpart so retrieve and update it. 

            share = Share.objects.get(name=s_in_pool, pool=pool) 

            # Initially default our pqgroup value to db default of '-1/-1' 

            # This way, unless quotas are enabled, all pqgroups will be 

            # returned to db default. 

            pqgroup = PQGROUP_DEFAULT 

            if share.pool.quotas_enabled: 

                # Quotas are enabled on our pool so we can validate pqgroup. 

                if share.pqgroup == pqgroup or not share.pqgroup_exist \ 

                        or share.pqgroup in share_pqgroups_used: 

                    # we have a void '-1/-1' or non existent pqgroup or 

                    # this pqgroup has already been seen / used in this pool. 

                    logger.debug('#### replacing void, non-existent, or ' 

                                 'duplicate pqgroup') 

                    pqgroup = qgroup_create(pool) 

                    if pqgroup is not PQGROUP_DEFAULT: 

                        update_quota(pool, pqgroup, share.size * 1024) 

                        share_pqgroup_assign(pqgroup, share) 

                else: 

                    # Our share's pqgroup looks OK so use it. 

                    pqgroup = share.pqgroup 

                # Record our use of this pqgroup to spot duplicates later. 

                share_pqgroups_used.append(deepcopy(share.pqgroup)) 

            if share.pqgroup != pqgroup: 

                # we need to update our share.pqgroup 

                share.pqgroup = pqgroup 

                share.save() 

            share.qgroup = shares_in_pool[s_in_pool] 

            rusage, eusage, pqgroup_rusage, pqgroup_eusage = \ 

                volume_usage(pool, share.qgroup, pqgroup) 

            if (rusage != share.rusage or eusage != share.eusage or 

               pqgroup_rusage != share.pqgroup_rusage or 

               pqgroup_eusage != share.pqgroup_eusage): 

                share.rusage = rusage 

                share.eusage = eusage 

                share.pqgroup_rusage = pqgroup_rusage 

                share.pqgroup_eusage = pqgroup_eusage 

                update_shareusage_db(s_in_pool, rusage, eusage) 

            else: 

                update_shareusage_db(s_in_pool, rusage, eusage, UPDATE_TS) 

            share.save() 

            continue 

        try: 

            logger.debug('No prior entries in scanned pool trying all pools.') 

            # Test (Try) for an existing system wide Share db entry. 

            cshare = Share.objects.get(name=s_in_pool) 

            # Get a list of Rockstor relevant subvols (ie shares and clones) 

            # for the prior existing db share entry's pool. 

            cshares_d = shares_info(cshare.pool) 

            if s_in_pool in cshares_d: 

                e_msg = ('Another pool ({}) has a Share with this same ' 

                         'name ({}) as this pool ({}). This configuration ' 

                         'is not supported. You can delete one of them ' 

                         'manually with the following command: ' 

                         '"btrfs subvol delete {}[pool name]/{}" WARNING this ' 

                         'will remove the entire contents of that subvolume.' 

                         .format(cshare.pool.name, s_in_pool, pool.name, 

                                 settings.MNT_PT, s_in_pool)) 

                handle_exception(Exception(e_msg), request) 

            else: 

                # Update the prior existing db share entry previously 

                # associated with another pool. 

                logger.debug('Updating prior db entry from another pool.') 

                cshare.pool = pool 

                cshare.qgroup = shares_in_pool[s_in_pool] 

                cshare.size = pool.size 

                cshare.subvol_name = s_in_pool 

                (cshare.rusage, cshare.eusage, cshare.pqgroup_rusage, 

                 cshare.pqgroup_eusage) = volume_usage(pool, cshare.qgroup, 

                                                       cshare.pqgroup) 

                cshare.save() 

                update_shareusage_db(s_in_pool, cshare.rusage, cshare.eusage) 

        except Share.DoesNotExist: 

            logger.debug('Db share entry does not exist - creating.') 

            # We have a share on disk that has no db counterpart so create one. 

            # Retrieve new pool quota id for use in db Share object creation. 

            # As the replication receive share is 'special' we tag it as such. 

            replica = False 

            share_name = s_in_pool 

            if re.match('.snapshot', s_in_pool) is not None: 

                # We have an initial replication share, non snap in .snapshots. 

                # We could change it's name here but still a little mixing 

                # of name and subvol throughout project. 

                replica = True 

                logger.debug('Initial receive quirk-subvol found: Importing ' 

                             'as share and setting replica flag.') 

            qid = shares_in_pool[s_in_pool] 

            pqid = qgroup_create(pool) 

            if pqid is not PQGROUP_DEFAULT: 

                update_quota(pool, pqid, pool.size * 1024) 

                pool_mnt_pt = '{}{}'.format(settings.MNT_PT, pool.name) 

                qgroup_assign(qid, pqid, pool_mnt_pt) 

            rusage, eusage, pqgroup_rusage, pqgroup_eusage = \ 

                volume_usage(pool, qid, pqid) 

            nso = Share(pool=pool, qgroup=qid, pqgroup=pqid, name=share_name, 

                        size=pool.size, subvol_name=s_in_pool, rusage=rusage, 

                        eusage=eusage, pqgroup_rusage=pqgroup_rusage, 

                        pqgroup_eusage=pqgroup_eusage, 

                        replica=replica) 

            nso.save() 

            update_shareusage_db(s_in_pool, rusage, eusage) 

            mount_share(nso, '%s%s' % (settings.MNT_PT, s_in_pool)) 

 

 

def import_snapshots(share): 

    snaps_d = snaps_info('%s%s' % (settings.MNT_PT, share.pool.name), 

                         share.name) 

    snaps = [s.name for s in Snapshot.objects.filter(share=share)] 

    for s in snaps: 

        if (s not in snaps_d): 

            logger.debug('Removing, missing on disk, snapshot db entry ({}) ' 

                         'from share ({}).'.format(s, share.name)) 

            Snapshot.objects.get(share=share, name=s).delete() 

    for s in snaps_d: 

        if (s in snaps): 

            so = Snapshot.objects.get(share=share, name=s) 

        else: 

            logger.debug('Adding, missing in db, on disk snapshot ({}) ' 

                         'against share ({}).'.format(s, share.name)) 

            so = Snapshot(share=share, name=s, real_name=s, 

                          writable=snaps_d[s][1], qgroup=snaps_d[s][0]) 

        rusage, eusage = volume_usage(share.pool, snaps_d[s][0]) 

        if (rusage != so.rusage or eusage != so.eusage): 

            so.rusage = rusage 

            so.eusage = eusage 

            update_shareusage_db(s, rusage, eusage) 

        else: 

            update_shareusage_db(s, rusage, eusage, UPDATE_TS) 

        so.save() 

 

 

def update_shareusage_db(subvol_name, rusage, eusage, new_entry=True): 

    """ 

    Creates a new share/subvol db usage entry, or updates an existing one with 

    a new time stamp and count increment. 

    The 'create new entry' mode is expected to be faster. 

    :param subvol_name: share/subvol name 

    :param rusage: Referenced/shared usage 

    :param eusage: Exclusive usage 

    :param new_entry: If True create a new entry with the passed params, 

    otherwise attempt to update the latest (by id) entry with time and count. 

    """ 

    ts = datetime.utcnow().replace(tzinfo=utc) 

    if new_entry: 

        su = ShareUsage(name=subvol_name, r_usage=rusage, e_usage=eusage, 

                        ts=ts) 

        su.save() 

    else: 

        try: 

            su = ShareUsage.objects.filter(name=subvol_name).latest('id') 

            su.ts = ts 

            su.count += 1 

        except ShareUsage.DoesNotExist: 

            su = ShareUsage(name=subvol_name, r_usage=rusage, e_usage=eusage, 

                            ts=ts) 

        finally: 

            su.save()