-
Notifications
You must be signed in to change notification settings - Fork 37
xrootd/{client,protocol}: add protocol request #195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a first pass.
I haven't completely reviewed the tests.
xrootd/client/protocol_mock_test.go
Outdated
} | ||
|
||
var want = ProtocolInfo{ | ||
protocolVersion, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add struct fields names here:
want := ProtocolInfo{
BinaryProtocolVersion: protocolVersion,
...
}
xrootd/client/protocol.go
Outdated
|
||
"go-hep.org/x/hep/xrootd/encoder" | ||
"go-hep.org/x/hep/xrootd/protocol" | ||
protocolRequest "go-hep.org/x/hep/xrootd/protocol/protocol" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package names are usually not "camel-cased".
could we think about a better name in this unfortunate name collision?
xproto
? protoreq
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or name the other import like:
import (
xrdproto "go-hep.org/x/hep/xrootd/protocol"
"go-hep.org/x/hep/xrootd/protocol/protocol"
)
?
xrootd/client/protocol.go
Outdated
var securityInfo *protocolRequest.SecurityInfo | ||
if len(resp) > protocolRequest.GeneralResponseLength { | ||
securityInfo = &protocolRequest.SecurityInfo{} | ||
err = encoder.Unmarshal(resp[protocolRequest.GeneralResponseLength:], securityInfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems we are dropping this error on the floor.
shouldn't we return it here ?
xrootd/client/protocol.go
Outdated
err = encoder.Unmarshal(resp[protocolRequest.GeneralResponseLength:], securityInfo) | ||
} | ||
|
||
var info = ProtocolInfo{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems we could create a ProtocolInfo
value with much of its fields already initialized to the correct values, like so:
info := ProtocolInfo{
BinaryProtocolVersion: generalResp.BinaryProtocolVersion,
ServerType: extractServerType(generalResp.Flags),
...
}
xrootd/client/protocol_test.go
Outdated
|
||
func TestClient_Protocol(t *testing.T) { | ||
var want = ProtocolInfo{ | ||
784, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use struct field names here as well.
xrootd/protocol/protocol/protocol.go
Outdated
*/ | ||
package protocol // import "go-hep.org/x/hep/xrootd/protocol/protocol" | ||
|
||
// RequestID is the id of the request, it is sent as part of message. See xrootd protocol specification for details. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See xrootd protocol specification for details.
please add either a link and/or the name of the section that deals with these details.
xrootd/protocol/protocol/protocol.go
Outdated
// Request is the struct that holds protocol request parameters. | ||
type Request struct { | ||
ClientProtocolVersion int32 | ||
Reserved1 [11]byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it works with the current reflect
-based code of the marshaling/unmarshaling protocol, we should rename these ReservedX
fields into just _
.
if this doesn't work with the reflect
-based code, then leave it like that but add a FIXME stating we should rename these into _
when the automatically generated (un)marshaling code is in place.
xrootd/protocol/protocol/protocol.go
Outdated
if withSecurityRequirements { | ||
options |= ReturnSecurityRequirements | ||
} | ||
return Request{protocolVersion, [11]byte{}, options, 0} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use struct field names here.
you can drop the ReservedX
fields:
return Request{ClientProtocolVersion: protocolVersion, Options:options}
35f6d09
to
922d570
Compare
@sbinet, PTAL. The problem with that request was in the difference between specification and actual implementation: // NOTE: That request differs from one specified in http://xrootd.org/doc/dev45/XRdv310.pdf, p. 71:
// order of Options and Reserved fields differs in the protocol specification and
// actual implementation which can be found at https://github.com/xrootd/xrootd/blob/master/src/XProtocol/XProtocol.hh
// (see ClientProtocolRequest definition).
type Request struct {
ClientProtocolVersion int32
Options RequestOptions
// FIXME: Rename Reserved* fields to _ when automatically generated (un)marshalling will be available.
Reserved1 [11]byte
Reserved2 int32
} Right now, I used the order of implementation and added a |
Yes I guess we should file an issue against xrootd. (Probably to change the specs to match the implementation :P) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should probably be rebased on top of 21c1f45
xrootd/client/protocol_mock_test.go
Outdated
serverFunc := func(cancel func(), conn net.Conn) { | ||
data, err := readRequest(conn) | ||
if err != nil { | ||
defer cancel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't be possible to just put one defer cancel()
at the top of the function ? (instead of all these scattered defer
s?)
xrootd/client/protocol_test.go
Outdated
|
||
client, err := NewClient(context.Background(), addr) | ||
if err != nil { | ||
t.Fatalf("cannon create client: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/cannon/could not/
xrootd/protocol/protocol/protocol.go
Outdated
Package protocol contains the structures describing request and response | ||
for protocol request (see XRootD specification). | ||
Response consist of 3 parts: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Reponse consist/A response consists/
@@ -0,0 +1,59 @@ | |||
// Copyright 2018 The go-hep Authors. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by convention, we don't use very much camelCase names for files.
I would either rename signRequirements.go
into signing.go
or signed_requirements.go
.
or, perhaps, just merge the content of that file at the bottom of protocol.go
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like not to put it into protocol.go
because I expect a lot of "configuration" code to be placed here to initialize the predefined levels. I'm fine with signing.go
. :)
xrootd/protocol/signRequirements.go
Outdated
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package protocol contains the XRootD protocol specific types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove this package-level comment as it may override the much nicer one that already exists in protocol.go
.
xrootd/protocol/protocol/protocol.go
Outdated
2) SecurityInfo - a response part that is added to general response | ||
if `ReturnSecurityRequirements` is provided and server supports it. | ||
It contains security version, security options, security level, | ||
and number of following security overrides, if any. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/and number/and the number/
xrootd/protocol/protocol/protocol.go
Outdated
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use //
instead of /* ... */
(for consistency's sake.)
xrootd/protocol/protocol/protocol.go
Outdated
type Flags int32 | ||
|
||
const ( | ||
// IsServer indicates whether this server has server role. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this whole block could be rewritten to look like:
const (
IsServer Flags = 0x00000001 // IsServer indicates whether this server has server role.
IsManager Flags = 0x00000002 // ...
...
)
xrootd/protocol/protocol/protocol.go
Outdated
// SecurityOverrideLength is the length of SecurityOverride in bytes. | ||
const SecurityOverrideLength = 2 | ||
|
||
// SecurityOverride is a alteration needed to the specified predefined security level. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/is a/is an/
xrootd/protocol/protocol/protocol.go
Outdated
const SecurityOverrideLength = 2 | ||
|
||
// SecurityOverride is a alteration needed to the specified predefined security level. | ||
// It consist of the request index and the security requirement that associated request is to have. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/It consist/It consists/
s/that associated/the associated/
s/is to have/should have/
I've filed xrootd/xrootd#717 |
922d570
to
a5167b8
Compare
Thank you very much for the review! I have rebased on top of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably this PR should also Updates go-hep/hep#170
(as it implements kXR_protocol
)
(also, probably #171
should be closed, right?)
xrootd/client/client.go
Outdated
} | ||
|
||
client := &Client{cancel, conn, mux.New(), 0} | ||
client := &Client{cancel: cancel, conn: conn, mux: mux.New(), signRequirements: protocol.DefaultSignRequirements()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we get away with just creating the zero value of protocol.SignRequirements
?
it seems client.signRequirements
is overwritten at line 73, below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, you are right.
// Protocol obtains the protocol version number, type of the server and security information, such as: | ||
// the security version, the security options, the security level, and the list of alterations | ||
// needed to the specified predefined security level. | ||
func (client *Client) Protocol(ctx context.Context) (ProtocolInfo, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering whether Protocol
should be a method on xrootd/client.Client
or a function of xrootd/protocol/protocol
...
if we keep Protocol
a method of client.Client
, then I suppose we'll have to have every single request/response as a method of client.Client
as well (ie 32 methods)
I am thinking out loud here (so it may just be a completely bunker idea), but it seems we could just get away with 1 or 2 methods in client.Client
and keep the rest in each of the xyz
protocol packages.
it seems to me this would introduce less coupling between client.Client
and each request/response.
for example:
xrootd/protocol/protocol
:
type Request { ... }
func NewRequest(...) Request { ... }
type Response { ... } // essentially client.ProtocolInfo
func Send(ctx context.Context, cli *xrdclient.Client, req Request) (Response, error) {
raw, err := cli.Call(ctx, req.ID(), req.Marshal(cli.Version()))
// ...
var resp Response
err = xrdproto.Unmarshal(&resp)
return resp, err
}
I thought that was what you were aiming at when you put each request-type into its own package.
that said, I am not completely happy with the Send
function as it is.
Perhaps, Send
should actually be a method of xrdclient.Client
, but it would take a (yet to be defined) xrdproto.Request
interface as argument:
func (cli *Client) Send(ctx context.Context, resp Response, req Request) error {
raw, err := cli.marshal(req)
recv, err := cli.call(ctx, req.ID(), raw)
err = cli.unmarshal(raw, resp)
return err
}
and used like so:
var resp protocol.Response
err := cli.Send(ctx, &resp, protocol.NewRequest(...))
from a user perspective, it would look better like so:
resp, err := cli.Send(ctx, protocol.NewRequest(...))
but one would have to type-assert to get the protocol.Response
type...
I am still on the fence.
happy to get your input :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds reasonable to me.
In fact, most of these Client.*
request methods will be pretty straightforward - create request, send it, parse and return response.
However, from the user perspective it is:
dirs, err := client.Dirlist(ctx, "/tmp")
vs
var resp dirlist.Response
err := client.Send(ctx, &resp, dirlist.NewRequest("/tmp"))
dirs := resp.Dirs
// (or without predeclaration but with type-assertion)
So we have a lot more boilerplate code from user perspective IMHO.
Also, it's much easier to type "client." and get a list of code completions than be like "What was the name of that request again?".
Actually, I think, we can implement both ways:
- After implementing a few more requests and the marshalling code generator, implement routine like this:
raw, err := client.marshal(req)
recv, err := client.call(...)
err = client.unmarshal(...)
return err
- Make small helper methods of
Client
:
func (client *Client) Dirlist(ctx context.Context, path string) ([]string, err) {
var resp dirlist.Response
err := client.Send(ctx, &resp, dirlist.NewRequest(path))
if err != nil {
return nil, err
}
return resp.Dirs, nil
}
At that moment, there is something like that actually, but with more request-specific code in the Client methods due to the incompleteness of the un(marshaller).
What do you think about it?
(moving the discussion to the main thread) I agree it's more verbose from the but, perhaps
what about, then: type File struct {
c *xrdclient.Client
name string
}
func (f *File) Readdirnames(n int) ([]string, error) {
var resp dirlist.Response
err := f.c.Send(ctx, &resp, dirlist.NewRequest(f.name))
return resp.Dirs, err
} |
I like that idea. There is a number of specific requests like:
that are more related to a "session" and they probably can reside inside Something like that for example: type Client struct {
// connection-wise info
}
func (c *Client) Ping(ctx context.Context) error {
// ...
}
type Filesystem struct {
c *Client
}
func (f *Filesystem) Dirlist(ctx context.Context) ([]string, error) {
// ...
}
type File struct {
c *Client
path string
}
func NewFile(client *Client, path string) File {
return File{client, path}
}
func (f *File) Read(ctx context.Context, offset int64, length int32) ([]byte, error) {
// ...
} However, var resp read.Response
err := client.Send(ctx, &resp, read.NewRequest(...)) Anyway, even with that additional argument in |
wrt (just nitpicking but: func (fs *Filesystem) Open(path string) (*File, error) { ... }
func (fs *Filesystem) Create(path string) (*File, error) { ... }
func (fs *Filesystem) OpenFile(path string, flag int, perm os.FileMode) (*File, error) { ... } (ie: I wouldn't have I think for the readahead list, we could as a first pass, just discard that feature. but we can table this for now :) |
a5167b8
to
6356063
Compare
@sbinet, sorry, what about merging this one? It looks like that we agree that it can be placed in I have updated PR with the fixes of other issues (somehow github managed to put that commit above our conversation, so I'm unsure if you noticed it). Commit messsage now includes "Updates #170" too. Regarding Client \ Filesystem \ File separation: I'll make that when I'll make a PRs for the corresponding requests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
(modulo once small last nitpick)
xrootd/protocol/protocol/protocol.go
Outdated
) | ||
|
||
// Request holds protocol request parameters. | ||
// NOTE: That request differs from one specified in http://xrootd.org/doc/dev45/XRdv310.pdf, p. 71: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the specs have been "fixed" to follow what's in the code :)
please remove.
Protocol request is used for obtaining the protocol version number, type of server and possible security requirements. These security requirements determine which requests should be signed. This info is provided to Client via protocol.SignRequirements. The Client will use sr.Needed to determine if the request should be signed via Sigver request. Actual sign requirements depending on the security level will be added as part of the request implementation in the following form: if level >= protocol.Pedantic { sr.requirements[dirlist.RequestID] = protocol.SignNeeded // ... } Updates go-hep#170.
6356063
to
c418617
Compare
FYI, the test failure in |
Add Protocol request which queries for such info as:
Information about security level and security overrides will further be used for signing requests when it's required by the server.
Client clean-up in the mock test is a bit awful due to not decided (yet) way of closing.
Response parsing is a bit awful too, since it copies data twice - from slice to the structs and then assembles these structs in more complicated response.
I want to make a generator of
Marshal
\Unmarshal
functions and handle bit-wise encoding and support of nested structs, slice length and so on.Something like:
Since I'm unsure that it is worth keeping a runtime-based Marshal\Unmarshal after generator will be written, I haven't added these features in it as part of that PR (or before that PR).
Regarding implementing the generator - I would like to deal with few complicated request before that to have better idea about possible features, if it is ok.