[Libguestfs] [PATCH nbdkit UNFINISHED] Add the ability to write plugins in golang.

Daniel P. Berrangé berrange at redhat.com
Wed Apr 15 16:27:07 UTC 2020


On Fri, Apr 10, 2020 at 02:51:52PM +0100, Richard W.M. Jones wrote:
> Similar to C, OCaml and Rust, this is not a plugin per se.  Instead
> it's more of a method and set of tests around writing plugins in
> golang.  They are standalone programs that compile into shared objects
> that nbdkit can then load (so there is no "go plugin" between nbdkit
> and the user plugin, unlike in scripting languages like Perl).

Why did you choose this approach ?

Looking at the code below there's the general boilerplate that
will be approx the same for all plugins with cut+paste tedium
across projects, and then there's the "interesting" code in
test.go

The methods in test.go though look quite unappealing from a
Go programmer's POV, as the API contracts are full of CGo
types and unsafe pointers.

To make something that is attractive for Go programmers, I
think it needs to hide all the low level CGo stuff entirely.

Implementing a nbdkit plugin should require nothing more
that providing pure Go code that satisfies  a well defined
Go "interface" type definition. There should not be any
copy+paste of example boilerplate, nor any use of CGo.

As an illustration, consider an interface and basic infrastructure:


  package nbdkitplugin


  type NBDKitFeature int
  const (
      NBDKitFeatureGetSize NBDKitFeature = iota
      NBDKitFeaturePread
      ...other optional methods...
  )

  type NBDKitPlugin interface {
      NBDGetFeatures() []NBDKitFeature
      NBDOpen(readonly bool)
      NBDClose()
      NBDGetSize() int64
      NBDPRead(count uint32, offset uint64, flags uint32) ([]byte)
      ....many other methods...
  }

  var plugin *C.struct_nbdkit_plugin
  var pluginImpl NBDKitPlugin

  func PluginInit(name string, impl NBDKitPlugin) {
      pluginImpl = impl

      features = impl.GetFeatures()
      plugin.name = C.CString(name)
      plugin.open = (*[0]byte)(C.open_wrapper)
      plugin.close = (*[0]byte)(C.close_wrapper)

      for _, feature := range features {
          switch feature {
              case NBDKitFeatureGetSize:
                plugin.get_size = (*[0]byte)(C.get_size_wrapper)
              case NBDKitFeaturePread:
                plugin.pread = (*[0]byte)(C.pread_wrapper)
          }
      }
      
  }

  ....all the methods like C.pread_wrapper/C.get_size_wrapper
      need to invoke pluginImpl methods....


  // This type implements all methods in NBDKitPlugin, with
  // no-op impls. This means that people implementing plugins
  // don't need to implement every method in the interface,
  // only the few they care about
  type PluginBase struct {
  }

  // We don't implement anytrhing by default
  func (plugin *PluginBase) NBDGetFeatures() []NBDKitFeature {
        return []NBDKitFeature{}
  }

  func (plugin *PluginBase) NBDOpen(readonly bool) {}
 
  func (plugin *PluginBase) NBDClose() {}
 
  func (plugin *PluginBase) NBDGetSize() int64 { return 0 }
  }
 
  func (plugin *PluginBase) NBDPRead(count uint32, offset uint64, flags uint32) []byte {
 	return []byte{}
  }

  ...  no-op impls of all other methods for NBDKitPlugin interface...


This code above would all be a standalone go module that is just
imported.


Now an impl of a plugin becomes just one single file

  import (
     libguestfs.org/nbdkitplugin
  )


  type TestPlugin struct {
      nbdkitplugin.PluginBase   // This provides no-op impls of all methods
      ...blah...
  }

  func NewTestPlugin() NBDKitPlugin {
      return &TestPlugin{
         ....blah...
      }
  }

  // This declares which methods we're actually implementing
  func (plugin *TestPlugin) NBDGetFeatures() []NBDKitFeature {
        return []NBDKitFeature{
	    NBDKitFeatureGetSize,
	    NBDKitFeaturePread,
	}
  }

  func (plugin *TestPlugin) NBDOpen(readonly bool) {
 	nbdkit.Debug("golang code running in the .open callback")
  }
 
  func  (plugin *TestPlugin) NBDClose() {
 	nbdkit.Debug("golang code running in the .close callback")
  }
 
  func (plugin *TestPlugin) NBDGetSize() int64 {
 	nbdkit.Debug("golang code running in the .get_size callback")
 	return 1024 * 1024
  }
 
  func (plugin *TestPlugin) NBDPRead(count uint32, offset uint64, flags uint32) []byte {
 	nbdkit.Debug("golang code running in the .pread callback")
 	return []byte{}
  }

   ... don't need to implement any other methods, since PluginBase
       satisfies the interface contract....

  func init() {
       nbdkitplugin.PluginInitialize("test", NewTestPlugin())
  }


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|




More information about the Libguestfs mailing list