Exemple #1
0
def check_disks(context, disks, cache_disks=None, log_disks=None):
    all_disks = [disk["path"] for disk in context.call_sync("disk.query")]
    available_disks = context.call_sync('volume.get_available_disks')
    if cache_disks is not None:
        for disk in cache_disks:
            disk = correct_disk_path(disk)
            if disk not in all_disks:
                raise CommandException(_("Disk {0} does not exist.".format(disk)))
            if disk in available_disks:
                available_disks.remove(disk)
            else:
                raise CommandException(_("Disk {0} is not available.".format(disk)))
    if log_disks is not None:
        for disk in log_disks:
            disk = correct_disk_path(disk)
            if disk not in all_disks:
                raise CommandException(_("Disk {0} does not exist.".format(disk)))
            if disk in available_disks:
                available_disks.remove(disk)
            else:
                raise CommandException(_("Disk {0} is not available.".format(disk)))
    if 'auto' in disks:
        return available_disks, cache_disks, log_disks
    else:
        for disk in disks:
            disk = correct_disk_path(disk)
            if disk not in all_disks:
                raise CommandException(_("Disk {0} does not exist.".format(disk)))
            if disk not in available_disks:
                raise CommandException(_("Disk {0} is not available.".format(disk)))
    return disks, cache_disks, log_disks
Exemple #2
0
    def run(self, context, args, kwargs, opargs):
        if 'vdev' not in kwargs:
            raise CommandException(_("Please specify a vdev to mirror to."))
        vdev_ident = correct_disk_path(kwargs.pop('vdev'))
        if len(args) < 1:
            raise CommandException(_("Please specify a disk to add to the vdev."))
        elif len(args) > 1:
            raise CommandException(_("Invalid input: {0}".format(args)))

        disk = correct_disk_path(args[0])
        if disk not in context.call_sync('volume.get_available_disks'):
            raise CommandException(_("Disk {0} is not available".format(disk)))

        vdev = first_or_default(lambda v:
                                v['path'] == vdev_ident or
                                vdev_ident in [i['path'] for i in v['children']],
                                self.parent.entity['topology']['data']
                                )

        if vdev['type'] == 'disk':
            vdev['type'] = 'mirror'
            vdev['children'].append({
                'type': 'disk',
                'path': vdev_ident
                })

        vdev['children'].append({
            'type': 'disk',
            'path': disk
        })

        self.parent.modified = True
        self.parent.save()
Exemple #3
0
    def run(self, context, args, kwargs, opargs):
        if 'vdev' not in kwargs:
            raise CommandException(_("Please specify a vdev to mirror to."))
        vdev_ident = correct_disk_path(kwargs.pop('vdev'))
        if len(args) < 1:
            raise CommandException(_("Please specify a disk to add to the vdev."))
        elif len(args) > 1:
            raise CommandException(_("Invalid input: {0}".format(args)))

        disk = correct_disk_path(args[0])
        if disk not in context.call_sync('volume.get_available_disks'):
            raise CommandException(_("Disk {0} is not available".format(disk)))

        vdev = first_or_default(lambda v:
                                v['path'] == vdev_ident or
                                vdev_ident in [i['path'] for i in v['children']],
                                self.parent.entity['topology']['data']
                                )

        if vdev['type'] == 'disk':
            vdev['type'] = 'mirror'
            vdev['children'].append({
                'type': 'disk',
                'path': vdev_ident
                })

        vdev['children'].append({
            'type': 'disk',
            'path': disk
        })

        self.parent.modified = True
        self.parent.save()
Exemple #4
0
def check_disks(context, disks, cache_disks=None, log_disks=None):
    all_disks = [disk["path"] for disk in context.call_sync("disk.query")]
    available_disks = context.call_sync('volume.get_available_disks')
    if cache_disks is not None:
        for disk in cache_disks:
            disk = correct_disk_path(disk)
            if disk not in all_disks:
                raise CommandException(_("Disk {0} does not exist.".format(disk)))
            if disk in available_disks:
                available_disks.remove(disk)
            else:
                raise CommandException(_("Disk {0} is not available.".format(disk)))
    if log_disks is not None:
        for disk in log_disks:
            disk = correct_disk_path(disk)
            if disk not in all_disks:
                raise CommandException(_("Disk {0} does not exist.".format(disk)))
            if disk in available_disks:
                available_disks.remove(disk)
            else:
                raise CommandException(_("Disk {0} is not available.".format(disk)))
    if 'auto' in disks:
        return 'auto', cache_disks, log_disks
    else:
        for disk in disks:
            disk = correct_disk_path(disk)
            if disk not in all_disks:
                raise CommandException(_("Disk {0} does not exist.".format(disk)))
            if disk not in available_disks:
                raise CommandException(_("Disk {0} is not available.".format(disk)))
    return disks, cache_disks, log_disks
Exemple #5
0
 def run(self, context, args, kwargs, opargs):
     if not args:
         raise CommandException("detach_disk requires more arguments.\n{0}".format(inspect.getdoc(self)))
     disk = args.pop(0)
     disk = correct_disk_path(disk)
     context.submit_task('boot.detach_disk', disk)
     return
 def run(self, context, args, kwargs, opargs):
     if not args:
         raise CommandException("detach_disk requires more arguments.\n{0}".format(inspect.getdoc(self)))
     disk = args.pop(0)
     disk = correct_disk_path(disk)
     context.submit_task('boot.detach_disk', disk)
     return
Exemple #7
0
    def run(self, context, args, kwargs, opargs):
        if not args:
            raise CommandException('Not enough arguments provided')

        disk = args.pop(0)
        disk = correct_disk_path(disk)
        tid = context.submit_task('boot.disk.detach', disk)
        return TaskPromise(context, tid)
Exemple #8
0
 def set_disks(self, entity, args):
     all_disks = [disk["path"] for disk in self.context.call_sync("disk.query")]
     disks = []
     for disk in args:
         disk = correct_disk_path(disk)
         if disk not in all_disks:
             raise CommandException(_("Invalid disk: {0}, see '/ disk show' for a list of disks".format(disk)))
         disks.append(disk)
     self.set_args(entity, disks, 'disks')
Exemple #9
0
    def run(self, context, args, kwargs, opargs):
        if not args:
            raise CommandException('Not enough arguments provided')

        disk = args.pop(0)
        disk = correct_disk_path(disk)
        all_disks = context.entity_subscribers['disk'].query(select='path')

        if disk not in all_disks:
            raise CommandException(f"Disk {disk} does not exist.")

        tid = context.submit_task('boot.disk.attach', disk)
        return TaskPromise(context, tid)
Exemple #10
0
 def set_disks(self, entity, args):
     all_disks = [
         disk["path"] for disk in self.context.call_sync("disk.query")
     ]
     disks = []
     for disk in args:
         disk = correct_disk_path(disk)
         if disk not in all_disks:
             raise CommandException(
                 _("Invalid disk: {0}, see '/ disk show' for a list of disks"
                   .format(disk)))
         disks.append(disk)
     self.set_args(entity, disks, 'disks')
Exemple #11
0
    def run(self, context, args, kwargs, opargs):
        if not args:
            output_msg("attach_disk requires more arguments.\n{0}".format(inspect.getdoc(self)))
            return
        disk = args.pop(0)
        # The all_disks below is a temporary fix, use this after "select" is working
        # all_disks = context.call_sync('disk.query', [], {"select":"path"})
        all_disks = [d["path"] for d in context.call_sync("disk.query")]
        available_disks = context.call_sync('volume.get_available_disks')
        disk = correct_disk_path(disk)
        if disk not in all_disks:
            output_msg("Disk " + disk + " does not exist.")
            return
        if disk not in available_disks:
            output_msg("Disk " + disk + " is not usable.")
            return

        context.submit_task('boot.disk.attach', disk)
        return
 def run(self, context, args, kwargs, opargs):
     if not args:
         output_msg("attach_disk requires more arguments.\n{0}".format(inspect.getdoc(self)))
         return
     disk = args.pop(0)
     # The all_disks below is a temporary fix, use this after "select" is working
     # all_disks = context.call_sync('disk.query', [], {"select":"path"})
     all_disks = [d["path"] for d in context.call_sync("disk.query")]
     available_disks = context.call_sync('volume.get_available_disks')
     disk = correct_disk_path(disk)
     if disk not in all_disks:
         output_msg("Disk " + disk + " does not exist.")
         return
     if disk not in available_disks:
         output_msg("Disk " + disk + " is not usable.")
         return
     volume = context.call_sync('zfs.pool.get_boot_pool')
     context.submit_task('boot.attach_disk', volume['groups']['data'][0]['guid'], disk)
     return
Exemple #13
0
    def run(self, context, args, kwargs, opargs):
        if len(args) == 0:
            raise CommandException(_("Please specify a disk"))
        disk = args[0]
        volume = self.parent.entity

        disk = correct_disk_path(disk)

        vdevs = list(iterate_vdevs(volume['topology']))
        guid = None
        for vdev in vdevs:
            if vdev['path'] == disk:
                guid = vdev['guid']
                break

        if guid is None:
            raise CommandException(_("Disk {0} is not part of the volume.".format(disk)))
        context.submit_task(
            'volume.vdev.online',
            self.parent.entity['id'],
            guid,
            callback=lambda s, t: post_save(self.parent, s, t)
        )
Exemple #14
0
    def run(self, context, args, kwargs, opargs):
        if len(args) == 0:
            raise CommandException(_("Please specify a disk"))
        disk = args[0]
        volume = self.parent.entity

        disk = correct_disk_path(disk)

        vdevs = list(iterate_vdevs(volume['topology']))
        guid = None
        for vdev in vdevs:
            if vdev['path'] == disk:
                guid = vdev['guid']
                break

        if guid is None:
            raise CommandException(_("Disk {0} is not part of the volume.".format(disk)))
        context.submit_task(
            'volume.vdev.online',
            self.parent.entity['id'],
            guid,
            callback=lambda s, t: post_save(self.parent, s, t)
        )
Exemple #15
0
    def run(self, context, args, kwargs, opargs):
        if len(args) < 1:
            raise CommandException('Not enough arguments passed')

        id = args[0]
        oldname = args[0]

        if 'key' in kwargs:
            if 'disks' not in kwargs:
                raise CommandException('You have to provide list of disks when importing an encrypted volume')

            disks = kwargs['disks']
            if isinstance(disks, str):
                disks = [disks]

            correct_disks = []
            for dname in disks:
                correct_disks.append(correct_disk_path(dname))

            encryption = {'key': kwargs['key'],
                          'disks': correct_disks}
            password = kwargs.get('password', None)
        else:
            encryption = {}
            password = None

            if not args[0].isdigit():
                vols = context.call_sync('volume.find')
                vol = first_or_default(lambda v: v['name'] == args[0], vols)
                if not vol:
                    raise CommandException('Importable volume {0} not found'.format(args[0]))

                id = vol['id']
                oldname = vol['name']

        context.submit_task('volume.import', id, kwargs.get('newname', oldname), {}, encryption, password)
Exemple #16
0
    def run(self, context, args, kwargs, opargs):
        if len(args) < 1:
            raise CommandException('Not enough arguments passed')

        id = args[0]
        oldname = args[0]

        if 'key' in kwargs:
            if 'disks' not in kwargs:
                raise CommandException('You have to provide list of disks when importing an encrypted volume')

            disks = kwargs['disks']
            if isinstance(disks, str):
                disks = [disks]

            correct_disks = []
            for dname in disks:
                correct_disks.append(correct_disk_path(dname))

            encryption = {'key': kwargs['key'],
                          'disks': correct_disks}
            password = kwargs.get('password', None)
        else:
            encryption = {}
            password = None

            if not args[0].isdigit():
                vols = context.call_sync('volume.find')
                vol = first_or_default(lambda v: v['name'] == args[0], vols)
                if not vol:
                    raise CommandException('Importable volume {0} not found'.format(args[0]))

                id = vol['id']
                oldname = vol['name']

        context.submit_task('volume.import', id, kwargs.get('newname', oldname), {}, encryption, password)
Exemple #17
0
    def run(self, context, args, kwargs, opargs):
        entity = self.parent.entity
        if 'type' not in kwargs:
            raise CommandException(_(
                "Please specify a type of vdev, see 'help add_vdev' for more information"
            ))

        disks_per_type = DISKS_PER_TYPE.copy()
        disks_per_type.pop('auto', None)
        disks_per_type.update({
            'log': 1,
            'cache': 1,
        })

        typ = kwargs.pop('type')

        if disks_per_type.get(typ) is None:
            raise CommandException(_("Invalid vdev type"))

        if 'disks' not in kwargs:
            raise CommandException(_("Please specify one or more disks using the disks property"))
        else:
            disks = check_disks(context, to_list(kwargs.pop('disks')))[0]

        if len(disks) < disks_per_type[typ]:
            raise CommandException(_(
                "Vdev of type {0} requires at least {1} disks".format(typ, disks_per_type[typ])
            ))

        if typ == 'mirror':
            entity['topology']['data'].append({
                'type': 'mirror',
                'children': [{'type': 'disk', 'path': correct_disk_path(x)} for x in disks]
            })

        if typ == 'disk':
            if len(disks) != 1:
                raise CommandException(_("Disk vdev consist of single disk"))

            entity['topology']['data'].append({
                'type': 'disk',
                'path': correct_disk_path(disks[0])
            })

        if typ == 'cache':
            if 'cache' not in entity:
                entity['topology']['cache'] = []

            entity['topology']['cache'].append({
                'type': 'disk',
                'path': correct_disk_path(disks[0])
            })

        if typ == 'log':
            if len(disks) != 1:
                raise CommandException(_("Log vdevs cannot be mirrored"))

            if 'log' not in entity:
                entity['topology']['log'] = []

            entity['topology']['log'].append({
                'type': 'disk',
                'path': correct_disk_path(disks[0])
            })

        if typ.startswith('raidz'):
            entity['topology']['data'].append({
                'type': typ,
                'children': [{'type': 'disk', 'path': correct_disk_path(x)} for x in disks]
            })

        self.parent.modified = True
        self.parent.save()
Exemple #18
0
    def run(self, context, args, kwargs, opargs):
        if not args and not kwargs:
            raise CommandException(_("create requires more arguments, see 'help create' for more information"))
        if len(args) > 1:
            raise CommandException(_("Wrong syntax for create, see 'help create' for more information"))

        # This magic below make either `create foo` or `create name=foo` work
        if len(args) == 1:
            # However, do not allow user to specify name as both implicit and explicit parameter as this suggests a mistake
            if 'name' in kwargs:
                raise CommandException(_("Both implicit and explicit 'name' parameters are specified."))
            else:
                kwargs[self.parent.primary_key.name] = args.pop(0)

        if 'name' not in kwargs:
            raise CommandException(_('Please specify a name for your pool'))
        else:
            name = kwargs.pop('name')

        volume_type = kwargs.pop('type', 'auto')
        if volume_type not in VDEV_TYPES:
            raise CommandException(_(
                "Invalid volume type {0}.  Should be one of: {1}".format(volume_type, VDEV_TYPES)
            ))

        if 'disks' not in kwargs:
            raise CommandException(_("Please specify one or more disks using the disks property"))
        else:
            disks = kwargs.pop('disks')
            if isinstance(disks, six.string_types):
                disks = [disks]

        if read_value(kwargs.pop('encryption', False), ValueType.BOOLEAN) is True:
            encryption = True
            password = kwargs.get('password', None)
        else:
            encryption = False
            password = None

        cache_disks = kwargs.pop('cache', [])
        log_disks = kwargs.pop('log', [])
        if cache_disks is None:
            cache_disks = []
        if log_disks is None:
            log_disks = []
        if isinstance(cache_disks, six.string_types):
            cache_disks = [cache_disks]
        if isinstance(log_disks, six.string_types):
            log_disks = [log_disks]

        ns = SingleItemNamespace(None, self.parent)
        ns.orig_entity = query.wrap(copy.deepcopy(self.parent.skeleton_entity))
        ns.entity = query.wrap(copy.deepcopy(self.parent.skeleton_entity))

        disks, cache_disks, log_disks = check_disks(context, disks, cache_disks, log_disks)

        if len(disks) < DISKS_PER_TYPE[volume_type]:
            raise CommandException(_("Volume type {0} requires at least {1} disks".format(volume_type,
                                                                                          DISKS_PER_TYPE[volume_type])))
        if len(disks) > 1 and volume_type == 'disk':
            raise CommandException(_("Cannot create a volume of type disk with multiple disks"))

        if volume_type == 'auto':
            layout = kwargs.pop('layout', 'auto')
            if layout not in VOLUME_LAYOUTS:
                raise CommandException(_(
                    "Invalid layout {0}.  Should be one of: {1}".format(layout, list(VOLUME_LAYOUTS.keys()))
                ))
            else:
                if len(disks) < DISKS_PER_TYPE[VOLUME_LAYOUTS[layout]]:
                    raise CommandException(_("Volume layout {0} requires at least {1} disks".format(layout, DISKS_PER_TYPE[VOLUME_LAYOUTS[layout]])))

            context.submit_task('volume.create_auto', name, 'zfs', layout, disks, cache_disks, log_disks, encryption, password)
        else:
            ns.entity['id'] = name
            ns.entity['topology'] = {}
            ns.entity['topology']['data'] = []
            if volume_type == 'disk':
                ns.entity['topology']['data'].append(
                    {'type': 'disk', 'path': correct_disk_path(disks[0])})
            else:
                ns.entity['topology']['data'].append({
                    'type': volume_type,
                    'children': [{'type': 'disk', 'path': correct_disk_path(disk)} for disk in disks]
                })
            ns.entity['encrypted'] = encryption
            if len(cache_disks) > 0:
                if 'cache' not in ns.entity:
                    ns.entity['topology']['cache'] = []

                for disk in cache_disks:
                    ns.entity['topology']['cache'].append({
                        'type': 'disk',
                        'path': correct_disk_path(disk)
                    })

            if len(log_disks) > 0:
                if 'log' not in ns.entity:
                    ns.entity['topology']['log'] = []

                if len(log_disks) > 1:
                    ns.entity['topology']['log'].append({
                        'type': 'mirror',
                        'children': [{'type': 'disk', 'path': correct_disk_path(disk)} for disk in log_disks]
                    })
                else:
                    ns.entity['topology']['log'].append({
                        'type': 'disk',
                        'path': correct_disk_path(log_disks[0])
                    })

            context.submit_task(
                self.parent.create_task,
                ns.entity,
                password,
                callback=lambda s, t: post_save(ns, s, t))
Exemple #19
0
    def run(self, context, args, kwargs, opargs):
        entity = self.parent.entity
        if 'type' not in kwargs:
            raise CommandException(_(
                "Please specify a type of vdev, see 'help add_vdev' for more information"
            ))

        disks_per_type = DISKS_PER_TYPE.copy()
        disks_per_type.pop('auto', None)
        disks_per_type.update({
            'log': 1,
            'cache': 1,
            'spare': 1
        })

        typ = kwargs.pop('type')

        if disks_per_type.get(typ) is None:
            raise CommandException(_("Invalid vdev type"))

        if 'disks' not in kwargs:
            raise CommandException(_("Please specify one or more disks using the disks property"))
        else:
            disks = check_disks(context, to_list(kwargs.pop('disks')))[0]

        if len(disks) < disks_per_type[typ]:
            raise CommandException(_(
                "Vdev of type {0} requires at least {1} disks".format(typ, disks_per_type[typ])
            ))

        if typ == 'mirror':
            entity['topology']['data'].append({
                'type': 'mirror',
                'children': [{'type': 'disk', 'path': correct_disk_path(x)} for x in disks]
            })

        if typ == 'disk':
            if len(disks) != 1:
                raise CommandException(_("Disk vdev consist of single disk"))

            entity['topology']['data'].append({
                'type': 'disk',
                'path': correct_disk_path(disks[0])
            })

        if typ == 'cache':
            if 'cache' not in entity:
                entity['topology']['cache'] = []

            entity['topology']['cache'].append({
                'type': 'disk',
                'path': correct_disk_path(disks[0])
            })

        if typ in ('log', 'spare'):
            if len(disks) != 1:
                raise CommandException(_("Log or spare vdevs cannot be mirrored"))

            if 'log' not in entity:
                entity['topology'][typ] = []

            entity['topology'][typ].append({
                'type': 'disk',
                'path': correct_disk_path(disks[0])
            })

        if typ.startswith('raidz'):
            entity['topology']['data'].append({
                'type': typ,
                'children': [{'type': 'disk', 'path': correct_disk_path(x)} for x in disks]
            })

        self.parent.modified = True
        self.parent.save()
Exemple #20
0
    def run(self, context, args, kwargs, opargs):
        if not args and not kwargs:
            raise CommandException(_("create requires more arguments, see 'help create' for more information"))
        if len(args) > 1:
            raise CommandException(_("Wrong syntax for create, see 'help create' for more information"))

        # This magic below make either `create foo` or `create name=foo` work
        if len(args) == 1:
            # However, do not allow user to specify name as both implicit and explicit parameter as this suggests a mistake
            if 'name' in kwargs:
                raise CommandException(_("Both implicit and explicit 'name' parameters are specified."))
            else:
                kwargs[self.parent.primary_key.name] = args.pop(0)

        if 'name' not in kwargs:
            raise CommandException(_('Please specify a name for your pool'))
        else:
            name = kwargs.pop('name')

        volume_type = kwargs.pop('type', 'auto')
        if volume_type not in VDEV_TYPES:
            raise CommandException(_(
                "Invalid volume type {0}.  Should be one of: {1}".format(volume_type, VDEV_TYPES)
            ))

        if 'disks' not in kwargs:
            raise CommandException(_("Please specify one or more disks using the disks property"))
        else:
            disks = kwargs.pop('disks')
            if isinstance(disks, six.string_types):
                disks = [disks]

        if read_value(kwargs.pop('encryption', False), ValueType.BOOLEAN) is True:
            encryption = True
            password = kwargs.get('password', None)
        else:
            encryption = False
            password = None

        cache_disks = kwargs.pop('cache', [])
        log_disks = kwargs.pop('log', [])
        if cache_disks is None:
            cache_disks = []
        if log_disks is None:
            log_disks = []
        if isinstance(cache_disks, six.string_types):
            cache_disks = [cache_disks]
        if isinstance(log_disks, six.string_types):
            log_disks = [log_disks]

        ns = SingleItemNamespace(None, self.parent)
        ns.orig_entity = query.wrap(copy.deepcopy(self.parent.skeleton_entity))
        ns.entity = query.wrap(copy.deepcopy(self.parent.skeleton_entity))

        disks, cache_disks, log_disks = check_disks(context, disks, cache_disks, log_disks)

        if disks != 'auto':
            if len(disks) < DISKS_PER_TYPE[volume_type]:
                raise CommandException(_("Volume type {0} requires at least {1} disks".format(
                    volume_type,
                    DISKS_PER_TYPE[volume_type]
                )))
            if len(disks) > 1 and volume_type == 'disk':
                raise CommandException(_("Cannot create a volume of type disk with multiple disks"))

        if volume_type == 'auto':
            layout = kwargs.pop('layout', 'auto')
            if layout not in VOLUME_LAYOUTS:
                raise CommandException(_(
                    "Invalid layout {0}.  Should be one of: {1}".format(layout, list(VOLUME_LAYOUTS.keys()))
                ))
            else:
                if disks != 'auto' and len(disks) < DISKS_PER_TYPE[VOLUME_LAYOUTS[layout]]:
                    raise CommandException(_("Volume layout {0} requires at least {1} disks".format(layout, DISKS_PER_TYPE[VOLUME_LAYOUTS[layout]])))

            context.submit_task('volume.create_auto', name, 'zfs', layout, disks, cache_disks, log_disks, encryption, password)
        else:
            ns.entity['id'] = name
            ns.entity['topology'] = {}
            ns.entity['topology']['data'] = []
            if volume_type == 'disk':
                ns.entity['topology']['data'].append(
                    {'type': 'disk', 'path': correct_disk_path(disks[0])})
            else:
                ns.entity['topology']['data'].append({
                    'type': volume_type,
                    'children': [{'type': 'disk', 'path': correct_disk_path(disk)} for disk in disks]
                })
            ns.entity['encrypted'] = encryption
            if len(cache_disks) > 0:
                if 'cache' not in ns.entity:
                    ns.entity['topology']['cache'] = []

                for disk in cache_disks:
                    ns.entity['topology']['cache'].append({
                        'type': 'disk',
                        'path': correct_disk_path(disk)
                    })

            if len(log_disks) > 0:
                if 'log' not in ns.entity:
                    ns.entity['topology']['log'] = []

                if len(log_disks) > 1:
                    ns.entity['topology']['log'].append({
                        'type': 'mirror',
                        'children': [{'type': 'disk', 'path': correct_disk_path(disk)} for disk in log_disks]
                    })
                else:
                    ns.entity['topology']['log'].append({
                        'type': 'disk',
                        'path': correct_disk_path(log_disks[0])
                    })

            context.submit_task(
                self.parent.create_task,
                ns.entity,
                password,
                callback=lambda s, t: post_save(ns, s, t))