Skip to content

Commit 7dba082

Browse files
committed
Add support for extended filesystem attributes ("XAttrs")
1 parent 1596ee1 commit 7dba082

File tree

3 files changed

+445
-2
lines changed

3 files changed

+445
-2
lines changed

error.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const (
99
fileNotFoundException = "java.io.FileNotFoundException"
1010
permissionDeniedException = "org.apache.hadoop.security.AccessControlException"
1111
pathIsNotEmptyDirException = "org.apache.hadoop.fs.PathIsNotEmptyDirectoryException"
12-
FileAlreadyExistsException = "org.apache.hadoop.fs.FileAlreadyExistsException"
12+
fileAlreadyExistsException = "org.apache.hadoop.fs.fileAlreadyExistsException"
1313
)
1414

1515
// Error represents a remote java exception from an HDFS namenode or datanode.
@@ -39,7 +39,7 @@ func interpretException(err error) error {
3939
return os.ErrPermission
4040
case pathIsNotEmptyDirException:
4141
return syscall.ENOTEMPTY
42-
case FileAlreadyExistsException:
42+
case fileAlreadyExistsException:
4343
return os.ErrExist
4444
default:
4545
return err

xattr.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package hdfs
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
hdfs "github.com/colinmarc/hdfs/v2/internal/protocol/hadoop_hdfs"
10+
"github.com/golang/protobuf/proto"
11+
)
12+
13+
var errXAttrKeysNotFound = errors.New("one or more keys not found")
14+
15+
const createAndReplace = 3
16+
17+
// ListXAttrs returns a list of all extended attributes for the given path.
18+
// The returned keys will be in the form
19+
func (c *Client) ListXAttrs(name string) (map[string]string, error) {
20+
req := &hdfs.ListXAttrsRequestProto{Src: proto.String(name)}
21+
resp := &hdfs.ListXAttrsResponseProto{}
22+
23+
err := c.namenode.Execute("listXAttrs", req, resp)
24+
if err != nil {
25+
return nil, &os.PathError{"list xattrs", name, interpretException(err)}
26+
}
27+
28+
return xattrMap(resp.GetXAttrs()), nil
29+
}
30+
31+
// GetXAttrs returns the extended attributes for the given path and list of
32+
// keys. The keys should be prefixed by namespace, e.g. user.foo or trusted.bar.
33+
func (c *Client) GetXAttrs(name string, keys ...string) (map[string]string, error) {
34+
if len(keys) == 0 {
35+
return make(map[string]string), nil
36+
}
37+
38+
req := &hdfs.GetXAttrsRequestProto{Src: proto.String(name)}
39+
for _, key := range keys {
40+
ns, rest, err := splitKey(key)
41+
if err != nil {
42+
return nil, &os.PathError{"get xattrs", name, err}
43+
}
44+
45+
req.XAttrs = append(req.XAttrs, &hdfs.XAttrProto{
46+
Namespace: ns,
47+
Name: proto.String(rest),
48+
})
49+
}
50+
resp := &hdfs.GetXAttrsResponseProto{}
51+
52+
err := c.namenode.Execute("getXAttrs", req, resp)
53+
if err != nil {
54+
if isKeyNotFound(err) {
55+
return nil, &os.PathError{"get xattrs", name, errXAttrKeysNotFound}
56+
}
57+
58+
return nil, &os.PathError{"get xattrs", name, interpretException(err)}
59+
}
60+
61+
return xattrMap(resp.GetXAttrs()), nil
62+
}
63+
64+
// SetXAttr sets an extended attribute for the given path and key. If the
65+
// attribute doesn't exist, it will be created.
66+
func (c *Client) SetXAttr(name, key, value string) error {
67+
resp := &hdfs.SetXAttrResponseProto{}
68+
69+
ns, rest, err := splitKey(key)
70+
if err != nil {
71+
return &os.PathError{"set xattr", name, err}
72+
}
73+
74+
req := &hdfs.SetXAttrRequestProto{
75+
Src: proto.String(name),
76+
XAttr: &hdfs.XAttrProto{
77+
Namespace: ns.Enum(),
78+
Name: proto.String(rest),
79+
Value: []byte(value),
80+
},
81+
Flag: proto.Uint32(createAndReplace),
82+
}
83+
84+
err = c.namenode.Execute("setXAttr", req, resp)
85+
if err != nil {
86+
return &os.PathError{"set xattr", name, interpretException(err)}
87+
}
88+
89+
return nil
90+
}
91+
92+
// RemoveXAttr unsets an extended attribute for the given path and key. It
93+
// returns an error if the attribute doesn't already exist.
94+
func (c *Client) RemoveXAttr(name, key string) error {
95+
ns, rest, err := splitKey(key)
96+
if err != nil {
97+
return &os.PathError{"remove xattr", name, err}
98+
}
99+
100+
req := &hdfs.RemoveXAttrRequestProto{
101+
Src: proto.String(name),
102+
XAttr: &hdfs.XAttrProto{
103+
Namespace: ns,
104+
Name: proto.String(rest),
105+
},
106+
}
107+
resp := &hdfs.RemoveXAttrResponseProto{}
108+
109+
err = c.namenode.Execute("removeXAttr", req, resp)
110+
if err != nil {
111+
if isKeyNotFound(err) {
112+
return &os.PathError{"remove xattr", name, errXAttrKeysNotFound}
113+
}
114+
115+
return &os.PathError{"remove xattr", name, interpretException(err)}
116+
}
117+
118+
return nil
119+
}
120+
121+
func splitKey(key string) (*hdfs.XAttrProto_XAttrNamespaceProto, string, error) {
122+
parts := strings.SplitN(key, ".", 2)
123+
if len(parts) < 2 {
124+
return nil, "", fmt.Errorf("invalid key: '%s'", key)
125+
}
126+
127+
var ns hdfs.XAttrProto_XAttrNamespaceProto
128+
switch strings.ToLower(parts[0]) {
129+
case "user":
130+
ns = hdfs.XAttrProto_USER
131+
case "trusted":
132+
ns = hdfs.XAttrProto_TRUSTED
133+
case "system":
134+
ns = hdfs.XAttrProto_SYSTEM
135+
case "security":
136+
ns = hdfs.XAttrProto_SECURITY
137+
case "raw":
138+
ns = hdfs.XAttrProto_RAW
139+
default:
140+
return nil, "", fmt.Errorf("invalid key namespace: '%s'", parts[0])
141+
}
142+
143+
return ns.Enum(), parts[1], nil
144+
}
145+
146+
func xattrMap(attrs []*hdfs.XAttrProto) map[string]string {
147+
m := make(map[string]string)
148+
for _, xattr := range attrs {
149+
key := fmt.Sprintf("%s.%s",
150+
strings.ToLower(xattr.GetNamespace().String()), xattr.GetName())
151+
m[key] = string(xattr.GetValue())
152+
}
153+
154+
return m
155+
}
156+
157+
func isKeyNotFound(err error) bool {
158+
if remoteErr, ok := err.(Error); ok {
159+
if strings.HasPrefix(remoteErr.Message(),
160+
"At least one of the attributes provided was not found") {
161+
return true
162+
}
163+
164+
if strings.HasPrefix(remoteErr.Message(),
165+
"No matching attributes found for remove operation") {
166+
return true
167+
}
168+
}
169+
170+
return false
171+
}

0 commit comments

Comments
 (0)