Rush has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/345631 )

Change subject: nfs-mounts: per cluster definitions for mounts
......................................................................

nfs-mounts: per cluster definitions for mounts

* create cluster mappings in nfs-mounts.yaml
* move VOLUMES_NEEDING_EXPORTS logic into nfs-mounts.yaml
* get_cluster() can determine what cluster a host is a member of
* bind_path is dynamic from nfs-mounts for nfs-manage-binds
* nfs-mounts.yaml path is an argument for nfs-manage-binds
* nfs-manage-binds '-binds' happens now before public export binds
* move device mappings to nfs-mounts as per cluster
* separate project metadata (gid) into separate dict in nfs-mounts
* 'mounts' in nfs-mounts.yaml now has per cluster designations
* mount_nfs_volume now search through cluster dicts for first match

Bug: T158883
Change-Id: Icbc20ec86ea7057df2262350d8c8d1a052f8c8ad
---
M modules/labstore/files/nfs-exportd
M modules/labstore/files/nfs-manage-binds
M modules/labstore/files/nfs-mounts.yaml
M modules/labstore/lib/puppet/parser/functions/mount_nfs_volume.rb
4 files changed, 358 insertions(+), 245 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/puppet 
refs/changes/31/345631/1

diff --git a/modules/labstore/files/nfs-exportd 
b/modules/labstore/files/nfs-exportd
index 20f82a8..6f977e1 100755
--- a/modules/labstore/files/nfs-exportd
+++ b/modules/labstore/files/nfs-exportd
@@ -26,6 +26,7 @@
 import time
 import logging
 import sys
+import socket
 import subprocess
 
 import keystoneauth1
@@ -33,9 +34,6 @@
 from keystoneclient.auth.identity.v3 import Password as KeystonePassword
 
 from novaclient import client as novaclient
-
-# Volumes that need an entry in exports.d
-VOLUMES_NEEDING_EXPORTS = ['project', 'home', 'tools-home', 'tools-project']
 
 
 def is_valid_ipv4(ip):
@@ -110,26 +108,30 @@
     return ips
 
 
-def get_projects_with_nfs(mounts_config, observer_pass):
-    """
-    Get populated project objects that need NFS exports
-    :param mounts_config: dict
+def get_projects_with_nfs(cluster_projects,
+                          cluster_mounts,
+                          allowed_private,
+                          observer_pass):
+    """ Get populated project objects that need NFS exports
+    :param cluster_projects: dict
+    :param cluster_mounts: dict
     :returns: list
     """
     projects = []
-    for name, config in mounts_config['private'].items():
-        if 'mounts' in config:
-            mounts = [k for k, v in config['mounts'].items()
-                      if k in VOLUMES_NEEDING_EXPORTS and v]
-            if len(mounts) == 0:
-                # Skip project if it has no private mounts
-                logging.debug('skipping exports for %s, no private mounts', 
name)
-                continue
-        else:
+    for name, mount_list in cluster_mounts.items():
+
+        # do not generate per project exportd output for either public
+        # exports or defined private that are not white listed
+        mounts = [m for m in mount_list if m in allowed_private]
+
+        if len(mounts) == 0:
+            # Skip project if it has no private mounts
+            logging.debug('skipping exports for %s, no private mounts', name)
             continue
+
         ips = get_instance_ips(name, observer_pass)
         if ips:
-            project = Project(name, config['gid'], ips, mounts)
+            project = Project(name, cluster_projects[name], ips, mounts)
             projects.append(project)
             logging.debug('project %s has %s instances',
                           name, len(project.instance_ips))
@@ -175,12 +177,20 @@
     return public_paths
 
 
-def write_project_exports(mounts_config, exports_d_path, observer_pass):
+def write_project_exports(cluster_projects,
+                          cluster_mounts,
+                          allowed_private,
+                          exports_d_path,
+                          observer_pass):
     """ output project export definitions
     :param mounts_config: dict of defined exports
     """
     project_paths = []
-    projects = get_projects_with_nfs(mounts_config, observer_pass)
+    projects = get_projects_with_nfs(cluster_projects,
+                                     cluster_mounts,
+                                     allowed_private,
+                                     observer_pass)
+
     for project in projects:
         logging.debug('writing exports file for %s', project.name)
         path = os.path.join(exports_d_path, '%s.exports' % project.name)
@@ -188,6 +198,16 @@
             f.write(project.get_exports())
         project_paths.append(path)
     return project_paths
+
+
+def get_cluster(clusters):
+    """:param clusters: dict
+    :returns: str
+    """
+    hostname = socket.gethostname()
+    for cluster, hosts in clusters.items():
+        if hostname in hosts:
+            return cluster
 
 
 def main():
@@ -243,14 +263,21 @@
             logging.exception('Could not load projects config file from %s', 
args.config_path)
             sys.exit(1)
 
+        cluster = get_cluster(config['clusters'])
+
         exports_d_path = args.exports_d_path
 
         existing_exports = [
             os.path.join(exports_d_path, filename)
             for filename in os.listdir(exports_d_path)]
 
-        public_paths = write_public_exports(config['public'], exports_d_path)
-        project_paths = write_project_exports(config, exports_d_path, 
args.observer_pass)
+        public_paths = write_public_exports(config['public'][cluster], 
exports_d_path)
+
+        project_paths = write_project_exports(config['projects'],
+                                              config['mounts'][cluster],
+                                              config['allowed_private_mounts'],
+                                              exports_d_path,
+                                              args.observer_pass)
 
         # compile list of entries in export_d path that are not defined in 
current config
         existing_wo_public = list(set(existing_exports) - set(public_paths))
diff --git a/modules/labstore/files/nfs-manage-binds 
b/modules/labstore/files/nfs-manage-binds
index 50d71ee..a5b37bb 100644
--- a/modules/labstore/files/nfs-manage-binds
+++ b/modules/labstore/files/nfs-manage-binds
@@ -3,6 +3,7 @@
 import os
 import logging
 import sys
+import socket
 import subprocess
 import yaml
 
@@ -71,15 +72,16 @@
                 ensure_dir('%s/home' % target)
             if 'project' in mounts:
                 ensure_dir('%s/project' % target)
+
         if os.path.exists(target):
             bind_mount(target, export)
         else:
             logging.warning("no bind on %s as %s does not exist" % (export, 
target))
 
 
-def get_binds():
-    """ find all bindmounts under /exp
-    :note: we assume /exp is the root for exported binds
+def get_binds(bind_root):
+    """ find all bindmounts under bind_root
+    :param bind_root: str
     :returns: list
     """
 
@@ -88,9 +90,18 @@
     mnt_targets = subprocess.check_output(cmd).decode()
     bind_mounts = []
     for mnt in mnt_targets.split():
-        if mnt.startswith('/exp'):
+        if mnt.startswith(bind_root):
             bind_mounts.append(mnt.strip())
     return bind_mounts
+
+def get_cluster(clusters):
+    """:param clusters: dict
+    :returns: str
+    """
+    hostname = socket.gethostname()
+    for cluster, hosts in clusters.items():
+        if hostname in hosts:
+            return cluster
 
 
 def main():
@@ -100,7 +111,7 @@
     argparser.add_argument(
         '-f',
         action='store_true',
-        help='New project directories (not /exp directories) need -f to be 
created.',
+        help='New project directories (not bind_path directories) need -f to 
be created.',
     )
 
     argparser.add_argument(
@@ -110,14 +121,20 @@
     )
 
     argparser.add_argument(
-        '-debug',
-        help='Turn on debug logging',
+        '-binds',
+        help='Display active binds (this operation will exit post)',
         action='store_true'
     )
 
     argparser.add_argument(
-        '-binds',
-        help='Display active binds (this operation will exit post)',
+        '-config_path',
+        default='/etc/nfs-mounts.yaml',
+        help='Path to YAML file containing config of which exports to 
maintain',
+    )
+
+    argparser.add_argument(
+        '-debug',
+        help='Turn on debug logging',
         action='store_true'
     )
 
@@ -135,52 +152,87 @@
         logging.warning("forcing creation for new project directories")
 
     try:
-        with open('/etc/nfs-mounts.yaml') as f:
+        with open(args.config_path) as f:
             config = yaml.safe_load(f)
     except:
         logging.exception('Could not load projects config file from %s', 
args.config_path)
         sys.exit(1)
 
-    if args.binds:
-        binds = get_binds()
-        if not binds:
-            sys.exit(1)
-        for b in binds:
-            print(b)
-        sys.exit(0)
+    cluster = get_cluster(config['clusters'])
 
+    if 'root' not in config['public'][cluster]:
+        logging.exception('No export root defined')
+        sys.exit(1)
+
+    public_binds = {}
     srv_root = args.disk_path
     # get dict of path_on_disk:share_name to create in descending order
-    public_inverse = {v.split(' ')[0]: k for k, v in config['public'].items()}
+    public_inverse = {v.split(' ')[0]: k for k, v in 
config['public'][cluster].items()}
     for k in sorted(public_inverse, key=len, reverse=False):
         path = k
         if public_inverse[k] == 'root':
-            exp_root = os.path.join('/', path)
+            exp_root = path
+            logging.info('Export root set to %s' % (exp_root,))
             if not os.path.exists(exp_root):
                 logging.error('The root export path does not exist')
                 sys.exit(1)
             continue
 
+        # process non-root public shares
         base = os.path.basename(path)
         exp = os.path.join(exp_root, base)
         srv = os.path.join(srv_root, base)
-        create_binding(srv, exp, force=args.f)
+        public_binds[srv] = exp
 
-    device_path_default = '/srv/misc'
-    device_paths = {
-        'tools': '/srv/tools',
-        'maps': '/srv/maps',
-    }
+    if args.binds:
+        binds = get_binds(exp_root)
+        if not binds:
+            logging.warning("No binds found at %s", exp_root)
+            sys.exit(1)
+        for b in binds:
+            print(b)
+        sys.exit(0)
 
-    for project in sorted(config['private']):
+    # set up non-project specific binds
+    for ppath, export in public_binds.items():
+        create_binding(ppath, export, force=args.f)
+
+    device_path_default = config['devices'][cluster]['default']
+    device_paths = config['devices'][cluster]['projects']
+
+    devices = list(device_paths.values()) + [device_path_default]
+    logging.debug("devices: %s" % (devices,))
+
+    for d in devices:
+        # Expect device paths to be mount points
+        if not is_mount(d):
+            logging.exception("%s is not a mount point" % d)
+            sys.exit(1)
+
+        # We do not export the root of these devices
+        # Expect a sysadmin has explicitly created a 'shared' dir
+        if not os.path.exists(os.path.join(d, 'shared')):
+            logging.exception("%s has no 'shared' directory" % d)
+            sys.exit(1)
+        else:
+            logging.debug("%s has a shared dir" % d)
+
+    for project in sorted(config['mounts'][cluster]):
+
         # Find which mounts should be made available for project
         # This is useful to create home and project dirs for new projects
-        mount_config = config['private'][project].get('mounts', {})
-        mounts = [mount for mount in mount_config.keys() if 
mount_config[mount]]
-
+        mounts = config['mounts'][cluster][project]
         srv_device = device_paths.get(project, device_path_default)
+
+        # If /dev/foo is mounted at /srv/foo then a 'shared' directory
+        # needs to be created /manually/ to seed the new device.
+
+        # Exported (via bind mount): /exp/myproject => 
/srv/foo/shared/myproject
+        # Example subtree (with subtree_check):
+        # * /srv/foo/shared/myproject/project
+        # * /srv/foo/shared/myproject/home
         srv = os.path.join(srv_device, 'shared', project)
-        exp = os.path.join('/exp/project', project)
+        exp = os.path.join(exp_root, 'project', project)
         create_binding(srv, exp, force=args.f, mounts=mounts)
 
 if __name__ == '__main__':
diff --git a/modules/labstore/files/nfs-mounts.yaml 
b/modules/labstore/files/nfs-mounts.yaml
index ef88c2b..aee26fc 100644
--- a/modules/labstore/files/nfs-mounts.yaml
+++ b/modules/labstore/files/nfs-mounts.yaml
@@ -1,188 +1,216 @@
+# Clusters share storage state
+clusters:
+    primary: ['labstore1001', 'labstore1002']
+    secondary: ['labstore1004', 'labstore1005']
+    # For now server side is not managed
+    # dyanmically.  Needs to be reimaged to Jessie.
+    tertiary: ['labstore1003']
+
+# Devices require a manual addition of a root
+# 'shared' directory to be viable for export on
+# per project resources
+devices:
+  secondary:
+    default: '/srv/misc'
+    projects:
+      tools: '/srv/tools'
+
+projects:
+  account-creation-assistance: 50088
+  bots: 50064
+  catgraph: 50588
+  tools: 50380
+  chasetestproject: 53308
+  contributors: 50408
+  cvn: 50116
+  wikidata-query: 52354
+  dumps: 50124
+  editor-engagement: 50068
+  etytree: 53231
+  fastcci: 50983
+  huggle: 50168
+  maps: 50196
+  maps-team: 52497
+  math: 50398
+  mwoffliner: 52376
+  openstack: 50076
+  osmit: 51933
+  project-proxy: 50254
+  quarry: 52243
+  snuggle: 50470
+  testlabs: 50302
+  tools: 50380
+  toolsbeta: 50610
+  twl: 52777
+  utrs: 50318
+  video: 52318
+  wikidata-dev: 50084
+  wikidata-topicmaps: 51980
+  wikidumpparse: 51824
+  wikisource-tools: 50336
+  wmt: 52488
+
+# A root must be defined here for every cluster
 public:
-  scratch: '/exp/scratch *(rw,sec=sys,sync,no_subtree_check,root_squash)'
-  root: '/exp *(ro,fsid=0,sec=sys,sync,subtree_check,root_squash,nocrossmnt)'
-private:
-  account-creation-assistance:
-    gid: 50088
-    mounts:
-      project: true
-  bots:
-    gid: 50064
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  catgraph:
-    gid: 50588
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  chasetestproject:
-    gid: 53308
-    mounts:
-      project: true
-  contributors:
-    gid: 50408
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  cvn:
-    gid: 50116
-    mounts:
-      project: true
-  wikidata-query:
-    gid: 52354
-    mounts:
-      scratch: true
-      dumps: true
-  dumps:
-    gid: 50124
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  editor-engagement:
-    gid: 50068
-    mounts:
-      project: true
-  etytree:
-    gid: 53231
-    mounts:
-      dumps: true
-  fastcci:
-    gid: 50983
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  huggle:
-    gid: 50168
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  maps:
-    gid: 50196
-    mounts:
-      dumps: true
-      scratch: true
-      maps: true
-  maps-team:
-    gid: 52497
-    mounts:
-      scratch: true
-  math:
-    gid: 50398
-    mounts:
-      project: true
-      scratch: true
-      dumps: true
-  mwoffliner:
-    gid: 52376
-    mounts:
-      scratch: true
-  openstack:
-    gid: 50076
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  osmit:
-    gid: 51933
-    mounts:
-      dumps: true
-  project-proxy:
-    gid: 50254
-    mounts:
-      project: true
-  quarry:
-    gid: 52243
-    mounts:
-      project: true
-  snuggle:
-    gid: 50470
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  testlabs:
-    gid: 50302
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  tools:
-    gid: 50380
-    mounts:
-      dumps: true
-      scratch: true
-      tools-home: true
-      tools-project: true
-  toolsbeta:
-    gid: 50610
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  twl:
-    gid: 52777
-    mounts:
-      project: true
-  utrs:
-    gid: 50318
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  video:
-    gid: 52318
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  wikidata-dev:
-    gid: 50084
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  wikidata-topicmaps:
-    gid: 51980
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  wikidumpparse:
-    gid: 51824
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  wikisource-tools:
-    gid: 50336
-    mounts:
-      dumps: true
-      home: true
-      project: true
-      scratch: true
-  wmt:
-    gid: 52488
-    mounts:
-      project: true
+  secondary:
+    root: '/exp *(ro,fsid=0,sec=sys,sync,subtree_check,root_squash,nocrossmnt)'
+  primary:
+    root: '/exp *(ro,fsid=0,sec=sys,sync,subtree_check,root_squash,nocrossmnt)'
+    #### scratch temporarily moved to labstore1003
+    # scratch: '/exp/scratch *(rw,sec=sys,sync,no_subtree_check,root_squash)'
+
+# Determines what can trigger a per project entry in exports.d/
+# even though we /end up mounting subpaths anyway/
+allowed_private_mounts:
+  - project
+  - home
+  - tools-home
+  - tools-project
+  - maps
+
+# Enabled mounts as read by both server and client side
+
+# Project additions to this file will not be active until 'nfs-manage-binds -f'
+# is run on the active server on the relevant cluster post addition.
+mounts:
+  secondary:
+    account-creation-assistance:
+      - project
+    bots:
+      - project
+      - home
+    catgraph:
+      - project
+      - home
+    chasetestproject:
+      - project
+    contributors:
+      - project
+      - home
+    cvn:
+      - project
+    wikidata-query:
+      - dumps
+    dumps:
+      - project
+      - home
+    editor-engagement:
+      - project
+    fastcci:
+      - project
+      - home
+    huggle:
+      - project
+      - home
+    math:
+      - project
+    openstack:
+      - project
+      - home
+    project-proxy:
+      - project
+    quarry:
+      - project
+    snuggle:
+      - project
+      - home
+    testlabs:
+      - project
+      - home
+    tools:
+      - tools-home
+      - tools-project
+    toolsbeta:
+      - project
+      - scratch
+    twl:
+      - project
+    utrs:
+      - home
+      - project
+    video:
+      - home
+      - project
+    wikidata-dev:
+      - home
+      - project
+    wikidata-topicmaps:
+      - home
+      - project
+    wikidumpparse:
+      - home
+      - project
+    wikisource-tools:
+      - home
+      - project
+    wmt:
+      - project
+  tertiary:
+    bots:
+      - dumps
+      - scratch
+    catgraph:
+      - dumps
+      - scratch
+    contributors:
+      - dumps
+      - scratch
+    wikidata-query:
+      - dumps
+      - scratch
+    dumps:
+      - dumps
+      - scratch
+    etytree:
+      - dumps
+    fastcci:
+      - dumps
+      - scratch
+    huggle:
+      - dumps
+      - scratch
+    maps:
+      - maps
+      - dumps
+      - scratch
+    maps-team:
+      - scratch
+    math:
+      - dumps
+      - scratch
+    mwoffliner:
+      - scratch
+    openstack:
+      - dumps
+      - scratch
+    osmit:
+      - dumps
+    snuggle:
+      - dumps
+      - scratch
+    testlabs:
+      - dumps
+      - scratch
+    tools:
+      - dumps
+      - scratch
+    toolsbeta:
+      - dumps
+    utrs:
+      - dumps
+      - scratch
+    video:
+      - dumps
+      - scratch
+    wikidata-dev:
+      - dumps
+      - scratch
+    wikidata-topicmaps:
+      - dumps
+      - scratch
+    wikidumpparse:
+      - dumps
+      - scratch
+    wikisource-tools:
+      - dumps
+      - scratch
diff --git a/modules/labstore/lib/puppet/parser/functions/mount_nfs_volume.rb 
b/modules/labstore/lib/puppet/parser/functions/mount_nfs_volume.rb
index 5f6f853..f46fd56 100644
--- a/modules/labstore/lib/puppet/parser/functions/mount_nfs_volume.rb
+++ b/modules/labstore/lib/puppet/parser/functions/mount_nfs_volume.rb
@@ -5,7 +5,7 @@
 # Returns true if the mount should be mounted in
 # instance of the project
 #
-# Reads this information from labstore/files/nfs-mounts-config.yaml
+# Reads this information from labstore/files/nfs-mounts.yaml
 # in the openstack module of operations/puppet.git
 module Puppet::Parser::Functions
   @@labs_nfs_config_touched = nil
@@ -18,14 +18,20 @@
         @@labs_nfs_config = function_loadyaml([path])
         @@labs_nfs_config_touched = mtime
     end
-    config = @@labs_nfs_config['private']
+
     project = args[0]
     mount = args[1]
-    if config.key?(project) && config[project].key?('mounts') \
-        && config[project]['mounts'].key?(mount)
-      config[project]['mounts'][mount]
-    else
-      false
+
+    clusters = @@labs_nfs_config['clusters']
+    clusters.each do |cluster, hosts|
+        if !labs_nfs_config['mounts'].key?(cluster)
+            next
+        end
+        cmounts = labs_nfs_config['mounts'][cluster]
+        if cmounts.key?(project) && cmounts[project].include?(mount)
+            return true
+        end
     end
+    return false
   end
 end

-- 
To view, visit https://gerrit.wikimedia.org/r/345631
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Icbc20ec86ea7057df2262350d8c8d1a052f8c8ad
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Rush <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to